From c1d777e2c5d91ddcafdeae47297db9ada032558f Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 18:08:18 +0100 Subject: [PATCH 01/18] Set coding style rules (editorconfig) --- .editorconfig | 155 ++++++++++++++++++++++++++++++++++++++++++++++++ MelonLoader.sln | 2 + 2 files changed, 157 insertions(+) diff --git a/.editorconfig b/.editorconfig index 8f0432e49..850612115 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,3 +2,158 @@ # CS1591: Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = none +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_simple_using_statement = true:warning +csharp_prefer_braces = when_multiline:silent +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = false:error +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_space_around_binary_operators = before_and_after +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_static_anonymous_function = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false:silent +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_switch_expression = true:warning +csharp_style_prefer_pattern_matching = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion +dotnet_diagnostic.CA1507.severity = warning +dotnet_diagnostic.CA1845.severity = warning + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +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 = warning +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 = warning +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.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 + +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.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_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = lf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = false:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +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:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:warning +dotnet_style_readonly_field = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +dotnet_style_allow_multiple_blank_lines_experimental = false:warning +dotnet_style_allow_statement_immediately_after_block_experimental = false:warning +dotnet_code_quality_unused_parameters = non_public:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning +dotnet_diagnostic.CA1000.severity = none +dotnet_diagnostic.CA1041.severity = warning +dotnet_diagnostic.CA1050.severity = error +dotnet_diagnostic.CA1707.severity = warning +dotnet_diagnostic.CA1708.severity = warning +dotnet_diagnostic.CA1712.severity = warning +dotnet_diagnostic.CA1827.severity = warning +dotnet_diagnostic.CA1828.severity = warning +dotnet_diagnostic.CA1829.severity = warning +dotnet_diagnostic.CA1832.severity = warning +dotnet_diagnostic.CA1833.severity = warning +dotnet_diagnostic.CA1858.severity = warning +dotnet_diagnostic.CA1868.severity = warning +dotnet_diagnostic.CA2011.severity = warning +dotnet_diagnostic.CA1816.severity = warning +dotnet_diagnostic.CA2249.severity = warning + +[*.vb] +dotnet_diagnostic.CA1047.severity = error \ No newline at end of file diff --git a/MelonLoader.sln b/MelonLoader.sln index c436069b2..8dd74f911 100644 --- a/MelonLoader.sln +++ b/MelonLoader.sln @@ -48,7 +48,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demeo", "Dependencies\Compa EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B2CA62DF-AD3A-47DD-8E85-B26A93C7BAE9}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .github\workflows\build.yml = .github\workflows\build.yml + Directory.Build.props = Directory.Build.props .github\workflows\nuget.yml = .github\workflows\nuget.yml README.md = README.md EndProjectSection From 717223473dfd67bce68d78f861faf6c09b66f0b3 Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 18:56:03 +0100 Subject: [PATCH 02/18] Move files to match namespace --- .../Demeo/Demeo_LobbyRequirement.cs | 13 +- .../IPA/IPA/IEnhancedPlugin.cs | 13 +- .../CompatibilityLayers/IPA/IPA/IPlugin.cs | 25 +-- .../CompatibilityLayers/IPA/IPA/ModPrefs.cs | 171 +++++++++--------- .../IPA/IPA/PluginManager.cs | 21 ++- .../Muse_Dash_Mono/MuseDashModLoader/IMod.cs | 19 +- .../MuseDashModLoader/ModLoader.cs | 47 ++--- .../MuseDashModLoader/ModLogger.cs | 27 +-- .../Muse_Dash_Mono/MuseDashModWrapper.cs | 11 +- .../BackwardsCompatibility/.editorconfig | 2 + MelonLoader/{Utils => }/EnumExtensions.cs | 0 .../HarmonyDontPatchAllAttribute.cs | 0 .../SharpZipLib/BZip2/BZip2.cs | 0 .../SharpZipLib/BZip2/BZip2Constants.cs | 0 .../SharpZipLib/BZip2/BZip2Exception.cs | 0 .../SharpZipLib/BZip2/BZip2InputStream.cs | 0 .../SharpZipLib/BZip2/BZip2OutputStream.cs | 0 .../SharpZipLib/Checksum/Adler32.cs | 0 .../SharpZipLib/Checksum/BZip2Crc.cs | 0 .../SharpZipLib/Checksum/Crc32.cs | 0 .../SharpZipLib/Checksum/CrcUtilities.cs | 0 .../SharpZipLib/Checksum/IChecksum.cs | 0 .../SharpZipLib/Core/EmptyRefs.cs | 0 .../Core/Exceptions/SharpZipBaseException.cs | 0 .../Exceptions/StreamDecodingException.cs | 0 .../Exceptions/StreamUnsupportedException.cs | 0 .../UnexpectedEndOfStreamException.cs | 0 .../Exceptions/ValueOutOfRangeException.cs | 0 .../SharpZipLib/Core/FileSystemScanner.cs | 0 .../SharpZipLib/Core/INameTransform.cs | 0 .../SharpZipLib/Core/IScanFilter.cs | 0 .../SharpZipLib/Core/InvalidNameException.cs | 0 .../SharpZipLib/Core/NameFilter.cs | 0 .../SharpZipLib/Core/PathFilter.cs | 0 .../SharpZipLib/Core/PathUtils.cs | 0 .../SharpZipLib/Core/StreamUtils.cs | 0 .../SharpZipLib/Encryption/PkzipClassic.cs | 0 .../SharpZipLib/Encryption/ZipAESStream.cs | 0 .../SharpZipLib/Encryption/ZipAESTransform.cs | 0 .../SharpZipLib/GZip/GZIPConstants.cs | 0 .../SharpZipLib/GZip/GZip.cs | 0 .../SharpZipLib/GZip/GZipException.cs | 0 .../SharpZipLib/GZip/GzipInputStream.cs | 0 .../SharpZipLib/GZip/GzipOutputStream.cs | 0 .../{ => ICSharpCode}/SharpZipLib/LICENSE.txt | 0 .../SharpZipLib/Lzw/LzwConstants.cs | 0 .../SharpZipLib/Lzw/LzwException.cs | 0 .../SharpZipLib/Lzw/LzwInputStream.cs | 0 .../{ => ICSharpCode}/SharpZipLib/README.md | 0 .../SharpZipLib/Tar/InvalidHeaderException.cs | 0 .../SharpZipLib/Tar/TarArchive.cs | 0 .../SharpZipLib/Tar/TarBuffer.cs | 0 .../SharpZipLib/Tar/TarEntry.cs | 0 .../SharpZipLib/Tar/TarException.cs | 0 .../Tar/TarExtendedHeaderReader.cs | 0 .../SharpZipLib/Tar/TarHeader.cs | 0 .../SharpZipLib/Tar/TarInputStream.cs | 0 .../SharpZipLib/Tar/TarOutputStream.cs | 0 .../SharpZipLib/Zip/Compression/Deflater.cs | 0 .../Zip/Compression/DeflaterConstants.cs | 0 .../Zip/Compression/DeflaterEngine.cs | 0 .../Zip/Compression/DeflaterHuffman.cs | 0 .../Zip/Compression/DeflaterPending.cs | 0 .../SharpZipLib/Zip/Compression/Inflater.cs | 0 .../Zip/Compression/InflaterDynHeader.cs | 0 .../Zip/Compression/InflaterHuffmanTree.cs | 0 .../Zip/Compression/PendingBuffer.cs | 0 .../Streams/DeflaterOutputStream.cs | 0 .../Streams/InflaterInputStream.cs | 0 .../Zip/Compression/Streams/OutputWindow.cs | 0 .../Compression/Streams/StreamManipulator.cs | 0 .../SharpZipLib/Zip/FastZip.cs | 0 .../SharpZipLib/Zip/IEntryFactory.cs | 0 .../SharpZipLib/Zip/WindowsNameTransform.cs | 0 .../SharpZipLib/Zip/ZipConstants.cs | 0 .../SharpZipLib/Zip/ZipEncryptionMethod.cs | 0 .../SharpZipLib/Zip/ZipEntry.cs | 0 .../SharpZipLib/Zip/ZipEntryExtensions.cs | 0 .../SharpZipLib/Zip/ZipEntryFactory.cs | 0 .../SharpZipLib/Zip/ZipException.cs | 0 .../SharpZipLib/Zip/ZipExtraData.cs | 0 .../SharpZipLib/Zip/ZipFile.cs | 0 .../SharpZipLib/Zip/ZipHelperStream.cs | 0 .../SharpZipLib/Zip/ZipInputStream.cs | 0 .../SharpZipLib/Zip/ZipNameTransform.cs | 0 .../SharpZipLib/Zip/ZipOutputStream.cs | 0 .../SharpZipLib/Zip/ZipStrings.cs | 0 .../ISupportModule_From.cs | 0 .../{SupportModule => }/ISupportModule_To.cs | 0 MelonLoader/{Utils => }/IniFile.cs | 0 MelonLoader/{Utils => }/InteropSupport.cs | 0 MelonLoader/{Lemons => }/LemonAction.cs | 0 MelonLoader/{Lemons => }/LemonArraySegment.cs | 0 MelonLoader/{Lemons => }/LemonEnumerator.cs | 0 MelonLoader/{Lemons => }/LemonFunc.cs | 0 MelonLoader/{Lemons => }/LemonTuple.cs | 0 MelonLoader/{Melons => }/Melon.cs | 0 .../{Melons/Events => }/MelonAction.cs | 0 .../MelonAdditionalCreditsAttribute.cs | 0 .../MelonAdditionalDependenciesAttribute.cs | 0 MelonLoader/{Melons => }/MelonAssembly.cs | 0 .../MelonAuthorColorAttribute.cs | 0 MelonLoader/{Melons => }/MelonBase.cs | 0 .../{Attributes => }/MelonColorAttribute.cs | 0 .../MelonCompatibilityLayer.cs | 0 MelonLoader/{Utils => }/MelonCoroutines.cs | 0 MelonLoader/{Utils => }/MelonDebug.cs | 0 MelonLoader/{Melons/Events => }/MelonEvent.cs | 0 .../{Attributes => }/MelonGameAttribute.cs | 0 .../MelonGameVersionAttribute.cs | 0 MelonLoader/{Melons => }/MelonHandler.cs | 0 .../{Attributes => }/MelonIDAttribute.cs | 0 .../MelonIncompatibleAssembliesAttribute.cs | 0 .../{Attributes => }/MelonInfoAttribute.cs | 0 MelonLoader/MelonLoader.csproj | 4 +- MelonLoader/{Utils => }/MelonLogger.cs | 0 MelonLoader/{Melons => }/MelonMod.cs | 0 .../MelonOptionalDependenciesAttribute.cs | 0 .../MelonPlatformAttribute.cs | 0 .../MelonPlatformDomainAttribute.cs | 0 MelonLoader/{Melons => }/MelonPlugin.cs | 0 .../{Preferences => }/MelonPreferences.cs | 0 .../MelonPreferences_Category.cs | 0 .../MelonPreferences_Entry.cs | 0 .../MelonPriorityAttribute.cs | 0 .../{Attributes => }/MelonProcessAttribute.cs | 0 MelonLoader/{Melons => }/MelonTypeBase.cs | 0 MelonLoader/{Utils => }/NativeLibrary.cs | 0 MelonLoader/{Attributes => }/PatchShield.cs | 0 .../{Attributes => }/RegisterTypeInIl2Cpp.cs | 0 .../RegisterTypeInIl2CppWithInterfaces.cs | 0 MelonLoader/{Melons => }/ResolvedMelons.cs | 0 MelonLoader/{Melons => }/RottenMelon.cs | 0 MelonLoader/Semver/IntExtensions.cs | 2 + MelonLoader/Semver/SemVersion.cs | 2 + .../{SupportModule => }/SupportModule.cs | 0 .../{SupportModule => }/SupportModule_From.cs | 0 .../TinyJSON/{Types => }/ProxyArray.cs | 0 .../TinyJSON/{Types => }/ProxyBoolean.cs | 0 .../TinyJSON/{Types => }/ProxyNumber.cs | 0 .../TinyJSON/{Types => }/ProxyObject.cs | 0 .../TinyJSON/{Types => }/ProxyString.cs | 0 MelonLoader/TinyJSON/{Types => }/Variant.cs | 0 MelonLoader/{Preferences => }/TomlMapper.cs | 0 .../VerifyLoaderBuildAttribute.cs | 0 .../VerifyLoaderVersionAttribute.cs | 0 146 files changed, 185 insertions(+), 172 deletions(-) create mode 100644 MelonLoader/BackwardsCompatibility/.editorconfig rename MelonLoader/{Utils => }/EnumExtensions.cs (100%) rename MelonLoader/{Attributes => }/HarmonyDontPatchAllAttribute.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/BZip2/BZip2.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/BZip2/BZip2Constants.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/BZip2/BZip2Exception.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/BZip2/BZip2InputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/BZip2/BZip2OutputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Checksum/Adler32.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Checksum/BZip2Crc.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Checksum/Crc32.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Checksum/CrcUtilities.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Checksum/IChecksum.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/EmptyRefs.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/Exceptions/StreamDecodingException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/FileSystemScanner.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/INameTransform.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/IScanFilter.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/InvalidNameException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/NameFilter.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/PathFilter.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/PathUtils.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Core/StreamUtils.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Encryption/PkzipClassic.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Encryption/ZipAESStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Encryption/ZipAESTransform.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/GZip/GZIPConstants.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/GZip/GZip.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/GZip/GZipException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/GZip/GzipInputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/GZip/GzipOutputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/LICENSE.txt (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Lzw/LzwConstants.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Lzw/LzwException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Lzw/LzwInputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/README.md (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/InvalidHeaderException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarArchive.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarBuffer.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarEntry.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarExtendedHeaderReader.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarHeader.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarInputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Tar/TarOutputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/Deflater.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/DeflaterConstants.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/DeflaterEngine.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/DeflaterHuffman.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/DeflaterPending.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/Inflater.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/InflaterDynHeader.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/PendingBuffer.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/FastZip.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/IEntryFactory.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/WindowsNameTransform.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipConstants.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipEncryptionMethod.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipEntry.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipEntryExtensions.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipEntryFactory.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipException.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipExtraData.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipFile.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipHelperStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipInputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipNameTransform.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipOutputStream.cs (100%) rename MelonLoader/{ => ICSharpCode}/SharpZipLib/Zip/ZipStrings.cs (100%) rename MelonLoader/{SupportModule => }/ISupportModule_From.cs (100%) rename MelonLoader/{SupportModule => }/ISupportModule_To.cs (100%) rename MelonLoader/{Utils => }/IniFile.cs (100%) rename MelonLoader/{Utils => }/InteropSupport.cs (100%) rename MelonLoader/{Lemons => }/LemonAction.cs (100%) rename MelonLoader/{Lemons => }/LemonArraySegment.cs (100%) rename MelonLoader/{Lemons => }/LemonEnumerator.cs (100%) rename MelonLoader/{Lemons => }/LemonFunc.cs (100%) rename MelonLoader/{Lemons => }/LemonTuple.cs (100%) rename MelonLoader/{Melons => }/Melon.cs (100%) rename MelonLoader/{Melons/Events => }/MelonAction.cs (100%) rename MelonLoader/{Attributes => }/MelonAdditionalCreditsAttribute.cs (100%) rename MelonLoader/{Attributes => }/MelonAdditionalDependenciesAttribute.cs (100%) rename MelonLoader/{Melons => }/MelonAssembly.cs (100%) rename MelonLoader/{Attributes => }/MelonAuthorColorAttribute.cs (100%) rename MelonLoader/{Melons => }/MelonBase.cs (100%) rename MelonLoader/{Attributes => }/MelonColorAttribute.cs (100%) rename MelonLoader/{CompatibilityLayers => }/MelonCompatibilityLayer.cs (100%) rename MelonLoader/{Utils => }/MelonCoroutines.cs (100%) rename MelonLoader/{Utils => }/MelonDebug.cs (100%) rename MelonLoader/{Melons/Events => }/MelonEvent.cs (100%) rename MelonLoader/{Attributes => }/MelonGameAttribute.cs (100%) rename MelonLoader/{Attributes => }/MelonGameVersionAttribute.cs (100%) rename MelonLoader/{Melons => }/MelonHandler.cs (100%) rename MelonLoader/{Attributes => }/MelonIDAttribute.cs (100%) rename MelonLoader/{Attributes => }/MelonIncompatibleAssembliesAttribute.cs (100%) rename MelonLoader/{Attributes => }/MelonInfoAttribute.cs (100%) rename MelonLoader/{Utils => }/MelonLogger.cs (100%) rename MelonLoader/{Melons => }/MelonMod.cs (100%) rename MelonLoader/{Attributes => }/MelonOptionalDependenciesAttribute.cs (100%) rename MelonLoader/{Attributes => }/MelonPlatformAttribute.cs (100%) rename MelonLoader/{Attributes => }/MelonPlatformDomainAttribute.cs (100%) rename MelonLoader/{Melons => }/MelonPlugin.cs (100%) rename MelonLoader/{Preferences => }/MelonPreferences.cs (100%) rename MelonLoader/{Preferences => }/MelonPreferences_Category.cs (100%) rename MelonLoader/{Preferences => }/MelonPreferences_Entry.cs (100%) rename MelonLoader/{Attributes => }/MelonPriorityAttribute.cs (100%) rename MelonLoader/{Attributes => }/MelonProcessAttribute.cs (100%) rename MelonLoader/{Melons => }/MelonTypeBase.cs (100%) rename MelonLoader/{Utils => }/NativeLibrary.cs (100%) rename MelonLoader/{Attributes => }/PatchShield.cs (100%) rename MelonLoader/{Attributes => }/RegisterTypeInIl2Cpp.cs (100%) rename MelonLoader/{Attributes => }/RegisterTypeInIl2CppWithInterfaces.cs (100%) rename MelonLoader/{Melons => }/ResolvedMelons.cs (100%) rename MelonLoader/{Melons => }/RottenMelon.cs (100%) rename MelonLoader/{SupportModule => }/SupportModule.cs (100%) rename MelonLoader/{SupportModule => }/SupportModule_From.cs (100%) rename MelonLoader/TinyJSON/{Types => }/ProxyArray.cs (100%) rename MelonLoader/TinyJSON/{Types => }/ProxyBoolean.cs (100%) rename MelonLoader/TinyJSON/{Types => }/ProxyNumber.cs (100%) rename MelonLoader/TinyJSON/{Types => }/ProxyObject.cs (100%) rename MelonLoader/TinyJSON/{Types => }/ProxyString.cs (100%) rename MelonLoader/TinyJSON/{Types => }/Variant.cs (100%) rename MelonLoader/{Preferences => }/TomlMapper.cs (100%) rename MelonLoader/{Attributes => }/VerifyLoaderBuildAttribute.cs (100%) rename MelonLoader/{Attributes => }/VerifyLoaderVersionAttribute.cs (100%) diff --git a/Dependencies/CompatibilityLayers/Demeo/Demeo_LobbyRequirement.cs b/Dependencies/CompatibilityLayers/Demeo/Demeo_LobbyRequirement.cs index ccdc98c80..bbbab6c34 100644 --- a/Dependencies/CompatibilityLayers/Demeo/Demeo_LobbyRequirement.cs +++ b/Dependencies/CompatibilityLayers/Demeo/Demeo_LobbyRequirement.cs @@ -1,10 +1,11 @@ using System; -namespace MelonLoader +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace MelonLoader; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +[AttributeUsage(AttributeTargets.Assembly)] +public class Demeo_LobbyRequirement : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class Demeo_LobbyRequirement : Attribute - { - public Demeo_LobbyRequirement() { } - } + public Demeo_LobbyRequirement() { } } diff --git a/Dependencies/CompatibilityLayers/IPA/IPA/IEnhancedPlugin.cs b/Dependencies/CompatibilityLayers/IPA/IPA/IEnhancedPlugin.cs index 2bb73efc2..1c20dc09f 100644 --- a/Dependencies/CompatibilityLayers/IPA/IPA/IEnhancedPlugin.cs +++ b/Dependencies/CompatibilityLayers/IPA/IPA/IEnhancedPlugin.cs @@ -1,8 +1,9 @@ -namespace IllusionPlugin +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace IllusionPlugin; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public interface IEnhancedPlugin : IPlugin { - public interface IEnhancedPlugin : IPlugin - { - string[] Filter { get; } - void OnLateUpdate(); - } + string[] Filter { get; } + void OnLateUpdate(); } diff --git a/Dependencies/CompatibilityLayers/IPA/IPA/IPlugin.cs b/Dependencies/CompatibilityLayers/IPA/IPA/IPlugin.cs index df0b97c48..0306eb75c 100644 --- a/Dependencies/CompatibilityLayers/IPA/IPA/IPlugin.cs +++ b/Dependencies/CompatibilityLayers/IPA/IPA/IPlugin.cs @@ -1,14 +1,15 @@ -namespace IllusionPlugin +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace IllusionPlugin; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public interface IPlugin { - public interface IPlugin - { - string Name { get; } - string Version { get; } - void OnApplicationStart(); - void OnApplicationQuit(); - void OnLevelWasLoaded(int level); - void OnLevelWasInitialized(int level); - void OnUpdate(); - void OnFixedUpdate(); - } + string Name { get; } + string Version { get; } + void OnApplicationStart(); + void OnApplicationQuit(); + void OnLevelWasLoaded(int level); + void OnLevelWasInitialized(int level); + void OnUpdate(); + void OnFixedUpdate(); } diff --git a/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs b/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs index 0f79ba121..bb06d52a9 100644 --- a/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs +++ b/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs @@ -1,98 +1,99 @@ using System; using MelonLoader; -namespace IllusionPlugin +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace IllusionPlugin; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public static class ModPrefs { - public static class ModPrefs - { - public static string GetString(string section, string name, string defaultValue = "", bool autoSave = false) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); - return entry.Value; - } + public static string GetString(string section, string name, string defaultValue = "", bool autoSave = false) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, defaultValue); + return entry.Value; + } - public static int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); - return entry.Value; - } + public static int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, defaultValue); + return entry.Value; + } - public static float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); - return entry.Value; - } + public static float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, defaultValue); + return entry.Value; + } - public static bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); - return entry.Value; - } + public static bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, defaultValue); + return entry.Value; + } - public static bool HasKey(string section, string name) => MelonPreferences.HasEntry(section, name); + public static bool HasKey(string section, string name) => MelonPreferences.HasEntry(section, name); - public static void SetFloat(string section, string name, float value) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); - entry.Value = value; - } + public static void SetFloat(string section, string name, float value) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, value); + entry.Value = value; + } - public static void SetInt(string section, string name, int value) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); - entry.Value = value; - } + public static void SetInt(string section, string name, int value) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, value); + entry.Value = value; + } - public static void SetString(string section, string name, string value) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); - entry.Value = value; - } + public static void SetString(string section, string name, string value) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, value); + entry.Value = value; + } - public static void SetBool(string section, string name, bool value) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); - entry.Value = value; - } - } + public static void SetBool(string section, string name, bool value) + { + MelonPreferences_Category category = MelonPreferences.GetCategory(section); + if (category == null) + category = MelonPreferences.CreateCategory(section); + MelonPreferences_Entry entry = category.GetEntry(name); + if (entry == null) + entry = category.CreateEntry(name, value); + entry.Value = value; + } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs b/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs index ab0f3fc42..45c3026b7 100644 --- a/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs +++ b/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs @@ -2,15 +2,16 @@ using IllusionPlugin; using MelonLoader.Utils; -namespace IllusionInjector +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace IllusionInjector; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public static class PluginManager { - public static class PluginManager - { - internal static List _Plugins = new List(); - public static IEnumerable Plugins { get => _Plugins; } - public class AppInfo - { - public static string StartupPath { get => MelonEnvironment.GameRootDirectory; } - } - } + internal static List _Plugins = new List(); + public static IEnumerable Plugins { get => _Plugins; } + public class AppInfo + { + public static string StartupPath { get => MelonEnvironment.GameRootDirectory; } + } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/IMod.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/IMod.cs index a33fe259f..1bd93f314 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/IMod.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/IMod.cs @@ -1,11 +1,12 @@ -namespace ModHelper +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ModHelper; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public interface IMod { - public interface IMod - { - string Name { get; } - string Description { get; } - string Author { get; } - string HomePage { get; } - void DoPatching(); - } + string Name { get; } + string Description { get; } + string Author { get; } + string HomePage { get; } + void DoPatching(); } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs index fef19fbcd..29e85fa0d 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs @@ -3,36 +3,37 @@ using MelonLoader; using ModHelper; -namespace ModLoader +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ModLoader; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public class ModLoader { - public class ModLoader + internal static List mods = new List(); + internal static Dictionary depends = new Dictionary(); + + public static void LoadDependency(Assembly assembly) { - internal static List mods = new List(); - internal static Dictionary depends = new Dictionary(); - - public static void LoadDependency(Assembly assembly) + foreach (string dependStr in assembly.GetManifestResourceNames()) { - foreach (string dependStr in assembly.GetManifestResourceNames()) + string filter = $"{assembly.GetName().Name}.Depends."; + if (dependStr.StartsWith(filter) && dependStr.EndsWith(".dll")) { - string filter = $"{assembly.GetName().Name}.Depends."; - if (dependStr.StartsWith(filter) && dependStr.EndsWith(".dll")) + string dependName = dependStr.Remove(dependStr.LastIndexOf(".dll")).Remove(0, filter.Length); + if (depends.ContainsKey(dependName)) { - string dependName = dependStr.Remove(dependStr.LastIndexOf(".dll")).Remove(0, filter.Length); - if (depends.ContainsKey(dependName)) - { - MelonLogger.Error($"Dependency conflict: {dependName} First at: {depends[dependName].GetName().Name}"); - continue; - } + MelonLogger.Error($"Dependency conflict: {dependName} First at: {depends[dependName].GetName().Name}"); + continue; + } - Assembly dependAssembly; - using (var stream = assembly.GetManifestResourceStream(dependStr)) - { - byte[] buffer = new byte[stream.Length]; - stream.Read(buffer, 0, buffer.Length); - dependAssembly = Assembly.Load(buffer); - } - depends.Add(dependName, dependAssembly); + Assembly dependAssembly; + using (var stream = assembly.GetManifestResourceStream(dependStr)) + { + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, buffer.Length); + dependAssembly = Assembly.Load(buffer); } + depends.Add(dependName, dependAssembly); } } } diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs index 5b4eacc98..ffa094d01 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs @@ -1,21 +1,22 @@ using System.Diagnostics; using MelonLoader; -namespace ModHelper +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace ModHelper; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public static class ModLogger { - public static class ModLogger + public static void Debug(object obj) { - public static void Debug(object obj) - { - var frame = new StackTrace().GetFrame(1); - var className = frame.GetMethod().ReflectedType.Name; - var methodName = frame.GetMethod().Name; - AddLog(className, methodName, obj); - } + var frame = new StackTrace().GetFrame(1); + var className = frame.GetMethod().ReflectedType.Name; + var methodName = frame.GetMethod().Name; + AddLog(className, methodName, obj); + } - public static void AddLog(string className, string methodName, object obj) - { - MelonLogger.Msg($"[{className}:{methodName}]: {obj}"); - } + public static void AddLog(string className, string methodName, object obj) + { + MelonLogger.Msg($"[{className}:{methodName}]: {obj}"); } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModWrapper.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModWrapper.cs index aef40b5d7..95bf38f52 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModWrapper.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModWrapper.cs @@ -1,10 +1,9 @@ using ModHelper; -namespace MelonLoader +namespace MelonLoader.CompatibilityLayers; + +internal class MuseDashModWrapper : MelonMod { - internal class MuseDashModWrapper : MelonMod - { - internal IMod modInstance; - public override void OnInitializeMelon() => modInstance.DoPatching(); - } + internal IMod modInstance; + public override void OnInitializeMelon() => modInstance.DoPatching(); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/.editorconfig b/MelonLoader/BackwardsCompatibility/.editorconfig new file mode 100644 index 000000000..a06223b95 --- /dev/null +++ b/MelonLoader/BackwardsCompatibility/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +dotnet_style_namespace_match_folder = false \ No newline at end of file diff --git a/MelonLoader/Utils/EnumExtensions.cs b/MelonLoader/EnumExtensions.cs similarity index 100% rename from MelonLoader/Utils/EnumExtensions.cs rename to MelonLoader/EnumExtensions.cs diff --git a/MelonLoader/Attributes/HarmonyDontPatchAllAttribute.cs b/MelonLoader/HarmonyDontPatchAllAttribute.cs similarity index 100% rename from MelonLoader/Attributes/HarmonyDontPatchAllAttribute.cs rename to MelonLoader/HarmonyDontPatchAllAttribute.cs diff --git a/MelonLoader/SharpZipLib/BZip2/BZip2.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs similarity index 100% rename from MelonLoader/SharpZipLib/BZip2/BZip2.cs rename to MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs diff --git a/MelonLoader/SharpZipLib/BZip2/BZip2Constants.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs similarity index 100% rename from MelonLoader/SharpZipLib/BZip2/BZip2Constants.cs rename to MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs diff --git a/MelonLoader/SharpZipLib/BZip2/BZip2Exception.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs similarity index 100% rename from MelonLoader/SharpZipLib/BZip2/BZip2Exception.cs rename to MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs diff --git a/MelonLoader/SharpZipLib/BZip2/BZip2InputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/BZip2/BZip2InputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs diff --git a/MelonLoader/SharpZipLib/BZip2/BZip2OutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/BZip2/BZip2OutputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs diff --git a/MelonLoader/SharpZipLib/Checksum/Adler32.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs similarity index 100% rename from MelonLoader/SharpZipLib/Checksum/Adler32.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs diff --git a/MelonLoader/SharpZipLib/Checksum/BZip2Crc.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs similarity index 100% rename from MelonLoader/SharpZipLib/Checksum/BZip2Crc.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs diff --git a/MelonLoader/SharpZipLib/Checksum/Crc32.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs similarity index 100% rename from MelonLoader/SharpZipLib/Checksum/Crc32.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs diff --git a/MelonLoader/SharpZipLib/Checksum/CrcUtilities.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs similarity index 100% rename from MelonLoader/SharpZipLib/Checksum/CrcUtilities.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs diff --git a/MelonLoader/SharpZipLib/Checksum/IChecksum.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs similarity index 100% rename from MelonLoader/SharpZipLib/Checksum/IChecksum.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs diff --git a/MelonLoader/SharpZipLib/Core/EmptyRefs.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/EmptyRefs.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs diff --git a/MelonLoader/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs diff --git a/MelonLoader/SharpZipLib/Core/Exceptions/StreamDecodingException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/Exceptions/StreamDecodingException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs diff --git a/MelonLoader/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs diff --git a/MelonLoader/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs diff --git a/MelonLoader/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs diff --git a/MelonLoader/SharpZipLib/Core/FileSystemScanner.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/FileSystemScanner.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs diff --git a/MelonLoader/SharpZipLib/Core/INameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/INameTransform.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs diff --git a/MelonLoader/SharpZipLib/Core/IScanFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/IScanFilter.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs diff --git a/MelonLoader/SharpZipLib/Core/InvalidNameException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/InvalidNameException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs diff --git a/MelonLoader/SharpZipLib/Core/NameFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/NameFilter.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs diff --git a/MelonLoader/SharpZipLib/Core/PathFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/PathFilter.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs diff --git a/MelonLoader/SharpZipLib/Core/PathUtils.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/PathUtils.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs diff --git a/MelonLoader/SharpZipLib/Core/StreamUtils.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs similarity index 100% rename from MelonLoader/SharpZipLib/Core/StreamUtils.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs diff --git a/MelonLoader/SharpZipLib/Encryption/PkzipClassic.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs similarity index 100% rename from MelonLoader/SharpZipLib/Encryption/PkzipClassic.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs diff --git a/MelonLoader/SharpZipLib/Encryption/ZipAESStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Encryption/ZipAESStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs diff --git a/MelonLoader/SharpZipLib/Encryption/ZipAESTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs similarity index 100% rename from MelonLoader/SharpZipLib/Encryption/ZipAESTransform.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs diff --git a/MelonLoader/SharpZipLib/GZip/GZIPConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs similarity index 100% rename from MelonLoader/SharpZipLib/GZip/GZIPConstants.cs rename to MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs diff --git a/MelonLoader/SharpZipLib/GZip/GZip.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs similarity index 100% rename from MelonLoader/SharpZipLib/GZip/GZip.cs rename to MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs diff --git a/MelonLoader/SharpZipLib/GZip/GZipException.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs similarity index 100% rename from MelonLoader/SharpZipLib/GZip/GZipException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs diff --git a/MelonLoader/SharpZipLib/GZip/GzipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/GZip/GzipInputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs diff --git a/MelonLoader/SharpZipLib/GZip/GzipOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/GZip/GzipOutputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs diff --git a/MelonLoader/SharpZipLib/LICENSE.txt b/MelonLoader/ICSharpCode/SharpZipLib/LICENSE.txt similarity index 100% rename from MelonLoader/SharpZipLib/LICENSE.txt rename to MelonLoader/ICSharpCode/SharpZipLib/LICENSE.txt diff --git a/MelonLoader/SharpZipLib/Lzw/LzwConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs similarity index 100% rename from MelonLoader/SharpZipLib/Lzw/LzwConstants.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs diff --git a/MelonLoader/SharpZipLib/Lzw/LzwException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Lzw/LzwException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs diff --git a/MelonLoader/SharpZipLib/Lzw/LzwInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Lzw/LzwInputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs diff --git a/MelonLoader/SharpZipLib/README.md b/MelonLoader/ICSharpCode/SharpZipLib/README.md similarity index 100% rename from MelonLoader/SharpZipLib/README.md rename to MelonLoader/ICSharpCode/SharpZipLib/README.md diff --git a/MelonLoader/SharpZipLib/Tar/InvalidHeaderException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/InvalidHeaderException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarArchive.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarArchive.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarBuffer.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarBuffer.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarEntry.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarExtendedHeaderReader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarExtendedHeaderReader.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarHeader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarHeader.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarInputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs diff --git a/MelonLoader/SharpZipLib/Tar/TarOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Tar/TarOutputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/Deflater.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/Deflater.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/DeflaterConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/DeflaterConstants.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/DeflaterEngine.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/DeflaterEngine.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/DeflaterHuffman.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/DeflaterHuffman.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/DeflaterPending.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/DeflaterPending.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/Inflater.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/Inflater.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/InflaterDynHeader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/InflaterDynHeader.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/PendingBuffer.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/PendingBuffer.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs diff --git a/MelonLoader/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs diff --git a/MelonLoader/SharpZipLib/Zip/FastZip.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/FastZip.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs diff --git a/MelonLoader/SharpZipLib/Zip/IEntryFactory.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/IEntryFactory.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs diff --git a/MelonLoader/SharpZipLib/Zip/WindowsNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/WindowsNameTransform.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipConstants.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipEncryptionMethod.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipEncryptionMethod.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipEntry.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipEntryExtensions.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipEntryExtensions.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipEntryFactory.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipEntryFactory.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipException.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipExtraData.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipExtraData.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipFile.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipFile.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipHelperStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipHelperStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipInputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipNameTransform.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipOutputStream.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs diff --git a/MelonLoader/SharpZipLib/Zip/ZipStrings.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs similarity index 100% rename from MelonLoader/SharpZipLib/Zip/ZipStrings.cs rename to MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs diff --git a/MelonLoader/SupportModule/ISupportModule_From.cs b/MelonLoader/ISupportModule_From.cs similarity index 100% rename from MelonLoader/SupportModule/ISupportModule_From.cs rename to MelonLoader/ISupportModule_From.cs diff --git a/MelonLoader/SupportModule/ISupportModule_To.cs b/MelonLoader/ISupportModule_To.cs similarity index 100% rename from MelonLoader/SupportModule/ISupportModule_To.cs rename to MelonLoader/ISupportModule_To.cs diff --git a/MelonLoader/Utils/IniFile.cs b/MelonLoader/IniFile.cs similarity index 100% rename from MelonLoader/Utils/IniFile.cs rename to MelonLoader/IniFile.cs diff --git a/MelonLoader/Utils/InteropSupport.cs b/MelonLoader/InteropSupport.cs similarity index 100% rename from MelonLoader/Utils/InteropSupport.cs rename to MelonLoader/InteropSupport.cs diff --git a/MelonLoader/Lemons/LemonAction.cs b/MelonLoader/LemonAction.cs similarity index 100% rename from MelonLoader/Lemons/LemonAction.cs rename to MelonLoader/LemonAction.cs diff --git a/MelonLoader/Lemons/LemonArraySegment.cs b/MelonLoader/LemonArraySegment.cs similarity index 100% rename from MelonLoader/Lemons/LemonArraySegment.cs rename to MelonLoader/LemonArraySegment.cs diff --git a/MelonLoader/Lemons/LemonEnumerator.cs b/MelonLoader/LemonEnumerator.cs similarity index 100% rename from MelonLoader/Lemons/LemonEnumerator.cs rename to MelonLoader/LemonEnumerator.cs diff --git a/MelonLoader/Lemons/LemonFunc.cs b/MelonLoader/LemonFunc.cs similarity index 100% rename from MelonLoader/Lemons/LemonFunc.cs rename to MelonLoader/LemonFunc.cs diff --git a/MelonLoader/Lemons/LemonTuple.cs b/MelonLoader/LemonTuple.cs similarity index 100% rename from MelonLoader/Lemons/LemonTuple.cs rename to MelonLoader/LemonTuple.cs diff --git a/MelonLoader/Melons/Melon.cs b/MelonLoader/Melon.cs similarity index 100% rename from MelonLoader/Melons/Melon.cs rename to MelonLoader/Melon.cs diff --git a/MelonLoader/Melons/Events/MelonAction.cs b/MelonLoader/MelonAction.cs similarity index 100% rename from MelonLoader/Melons/Events/MelonAction.cs rename to MelonLoader/MelonAction.cs diff --git a/MelonLoader/Attributes/MelonAdditionalCreditsAttribute.cs b/MelonLoader/MelonAdditionalCreditsAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonAdditionalCreditsAttribute.cs rename to MelonLoader/MelonAdditionalCreditsAttribute.cs diff --git a/MelonLoader/Attributes/MelonAdditionalDependenciesAttribute.cs b/MelonLoader/MelonAdditionalDependenciesAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonAdditionalDependenciesAttribute.cs rename to MelonLoader/MelonAdditionalDependenciesAttribute.cs diff --git a/MelonLoader/Melons/MelonAssembly.cs b/MelonLoader/MelonAssembly.cs similarity index 100% rename from MelonLoader/Melons/MelonAssembly.cs rename to MelonLoader/MelonAssembly.cs diff --git a/MelonLoader/Attributes/MelonAuthorColorAttribute.cs b/MelonLoader/MelonAuthorColorAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonAuthorColorAttribute.cs rename to MelonLoader/MelonAuthorColorAttribute.cs diff --git a/MelonLoader/Melons/MelonBase.cs b/MelonLoader/MelonBase.cs similarity index 100% rename from MelonLoader/Melons/MelonBase.cs rename to MelonLoader/MelonBase.cs diff --git a/MelonLoader/Attributes/MelonColorAttribute.cs b/MelonLoader/MelonColorAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonColorAttribute.cs rename to MelonLoader/MelonColorAttribute.cs diff --git a/MelonLoader/CompatibilityLayers/MelonCompatibilityLayer.cs b/MelonLoader/MelonCompatibilityLayer.cs similarity index 100% rename from MelonLoader/CompatibilityLayers/MelonCompatibilityLayer.cs rename to MelonLoader/MelonCompatibilityLayer.cs diff --git a/MelonLoader/Utils/MelonCoroutines.cs b/MelonLoader/MelonCoroutines.cs similarity index 100% rename from MelonLoader/Utils/MelonCoroutines.cs rename to MelonLoader/MelonCoroutines.cs diff --git a/MelonLoader/Utils/MelonDebug.cs b/MelonLoader/MelonDebug.cs similarity index 100% rename from MelonLoader/Utils/MelonDebug.cs rename to MelonLoader/MelonDebug.cs diff --git a/MelonLoader/Melons/Events/MelonEvent.cs b/MelonLoader/MelonEvent.cs similarity index 100% rename from MelonLoader/Melons/Events/MelonEvent.cs rename to MelonLoader/MelonEvent.cs diff --git a/MelonLoader/Attributes/MelonGameAttribute.cs b/MelonLoader/MelonGameAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonGameAttribute.cs rename to MelonLoader/MelonGameAttribute.cs diff --git a/MelonLoader/Attributes/MelonGameVersionAttribute.cs b/MelonLoader/MelonGameVersionAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonGameVersionAttribute.cs rename to MelonLoader/MelonGameVersionAttribute.cs diff --git a/MelonLoader/Melons/MelonHandler.cs b/MelonLoader/MelonHandler.cs similarity index 100% rename from MelonLoader/Melons/MelonHandler.cs rename to MelonLoader/MelonHandler.cs diff --git a/MelonLoader/Attributes/MelonIDAttribute.cs b/MelonLoader/MelonIDAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonIDAttribute.cs rename to MelonLoader/MelonIDAttribute.cs diff --git a/MelonLoader/Attributes/MelonIncompatibleAssembliesAttribute.cs b/MelonLoader/MelonIncompatibleAssembliesAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonIncompatibleAssembliesAttribute.cs rename to MelonLoader/MelonIncompatibleAssembliesAttribute.cs diff --git a/MelonLoader/Attributes/MelonInfoAttribute.cs b/MelonLoader/MelonInfoAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonInfoAttribute.cs rename to MelonLoader/MelonInfoAttribute.cs diff --git a/MelonLoader/MelonLoader.csproj b/MelonLoader/MelonLoader.csproj index d5bcc2ef7..9a5d107e2 100644 --- a/MelonLoader/MelonLoader.csproj +++ b/MelonLoader/MelonLoader.csproj @@ -62,7 +62,7 @@ - - + + \ No newline at end of file diff --git a/MelonLoader/Utils/MelonLogger.cs b/MelonLoader/MelonLogger.cs similarity index 100% rename from MelonLoader/Utils/MelonLogger.cs rename to MelonLoader/MelonLogger.cs diff --git a/MelonLoader/Melons/MelonMod.cs b/MelonLoader/MelonMod.cs similarity index 100% rename from MelonLoader/Melons/MelonMod.cs rename to MelonLoader/MelonMod.cs diff --git a/MelonLoader/Attributes/MelonOptionalDependenciesAttribute.cs b/MelonLoader/MelonOptionalDependenciesAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonOptionalDependenciesAttribute.cs rename to MelonLoader/MelonOptionalDependenciesAttribute.cs diff --git a/MelonLoader/Attributes/MelonPlatformAttribute.cs b/MelonLoader/MelonPlatformAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonPlatformAttribute.cs rename to MelonLoader/MelonPlatformAttribute.cs diff --git a/MelonLoader/Attributes/MelonPlatformDomainAttribute.cs b/MelonLoader/MelonPlatformDomainAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonPlatformDomainAttribute.cs rename to MelonLoader/MelonPlatformDomainAttribute.cs diff --git a/MelonLoader/Melons/MelonPlugin.cs b/MelonLoader/MelonPlugin.cs similarity index 100% rename from MelonLoader/Melons/MelonPlugin.cs rename to MelonLoader/MelonPlugin.cs diff --git a/MelonLoader/Preferences/MelonPreferences.cs b/MelonLoader/MelonPreferences.cs similarity index 100% rename from MelonLoader/Preferences/MelonPreferences.cs rename to MelonLoader/MelonPreferences.cs diff --git a/MelonLoader/Preferences/MelonPreferences_Category.cs b/MelonLoader/MelonPreferences_Category.cs similarity index 100% rename from MelonLoader/Preferences/MelonPreferences_Category.cs rename to MelonLoader/MelonPreferences_Category.cs diff --git a/MelonLoader/Preferences/MelonPreferences_Entry.cs b/MelonLoader/MelonPreferences_Entry.cs similarity index 100% rename from MelonLoader/Preferences/MelonPreferences_Entry.cs rename to MelonLoader/MelonPreferences_Entry.cs diff --git a/MelonLoader/Attributes/MelonPriorityAttribute.cs b/MelonLoader/MelonPriorityAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonPriorityAttribute.cs rename to MelonLoader/MelonPriorityAttribute.cs diff --git a/MelonLoader/Attributes/MelonProcessAttribute.cs b/MelonLoader/MelonProcessAttribute.cs similarity index 100% rename from MelonLoader/Attributes/MelonProcessAttribute.cs rename to MelonLoader/MelonProcessAttribute.cs diff --git a/MelonLoader/Melons/MelonTypeBase.cs b/MelonLoader/MelonTypeBase.cs similarity index 100% rename from MelonLoader/Melons/MelonTypeBase.cs rename to MelonLoader/MelonTypeBase.cs diff --git a/MelonLoader/Utils/NativeLibrary.cs b/MelonLoader/NativeLibrary.cs similarity index 100% rename from MelonLoader/Utils/NativeLibrary.cs rename to MelonLoader/NativeLibrary.cs diff --git a/MelonLoader/Attributes/PatchShield.cs b/MelonLoader/PatchShield.cs similarity index 100% rename from MelonLoader/Attributes/PatchShield.cs rename to MelonLoader/PatchShield.cs diff --git a/MelonLoader/Attributes/RegisterTypeInIl2Cpp.cs b/MelonLoader/RegisterTypeInIl2Cpp.cs similarity index 100% rename from MelonLoader/Attributes/RegisterTypeInIl2Cpp.cs rename to MelonLoader/RegisterTypeInIl2Cpp.cs diff --git a/MelonLoader/Attributes/RegisterTypeInIl2CppWithInterfaces.cs b/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs similarity index 100% rename from MelonLoader/Attributes/RegisterTypeInIl2CppWithInterfaces.cs rename to MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs diff --git a/MelonLoader/Melons/ResolvedMelons.cs b/MelonLoader/ResolvedMelons.cs similarity index 100% rename from MelonLoader/Melons/ResolvedMelons.cs rename to MelonLoader/ResolvedMelons.cs diff --git a/MelonLoader/Melons/RottenMelon.cs b/MelonLoader/RottenMelon.cs similarity index 100% rename from MelonLoader/Melons/RottenMelon.cs rename to MelonLoader/RottenMelon.cs diff --git a/MelonLoader/Semver/IntExtensions.cs b/MelonLoader/Semver/IntExtensions.cs index a1cfdaeca..691cf0a8d 100644 --- a/MelonLoader/Semver/IntExtensions.cs +++ b/MelonLoader/Semver/IntExtensions.cs @@ -1,6 +1,8 @@ using System.Text; +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace Semver +#pragma warning restore IDE0130 // Namespace does not match folder structure { internal static class IntExtensions { diff --git a/MelonLoader/Semver/SemVersion.cs b/MelonLoader/Semver/SemVersion.cs index 72bdc9c90..ea64c9ef2 100644 --- a/MelonLoader/Semver/SemVersion.cs +++ b/MelonLoader/Semver/SemVersion.cs @@ -7,7 +7,9 @@ #endif using System.Text.RegularExpressions; +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace Semver +#pragma warning restore IDE0130 // Namespace does not match folder structure { /// /// A semantic version implementation. diff --git a/MelonLoader/SupportModule/SupportModule.cs b/MelonLoader/SupportModule.cs similarity index 100% rename from MelonLoader/SupportModule/SupportModule.cs rename to MelonLoader/SupportModule.cs diff --git a/MelonLoader/SupportModule/SupportModule_From.cs b/MelonLoader/SupportModule_From.cs similarity index 100% rename from MelonLoader/SupportModule/SupportModule_From.cs rename to MelonLoader/SupportModule_From.cs diff --git a/MelonLoader/TinyJSON/Types/ProxyArray.cs b/MelonLoader/TinyJSON/ProxyArray.cs similarity index 100% rename from MelonLoader/TinyJSON/Types/ProxyArray.cs rename to MelonLoader/TinyJSON/ProxyArray.cs diff --git a/MelonLoader/TinyJSON/Types/ProxyBoolean.cs b/MelonLoader/TinyJSON/ProxyBoolean.cs similarity index 100% rename from MelonLoader/TinyJSON/Types/ProxyBoolean.cs rename to MelonLoader/TinyJSON/ProxyBoolean.cs diff --git a/MelonLoader/TinyJSON/Types/ProxyNumber.cs b/MelonLoader/TinyJSON/ProxyNumber.cs similarity index 100% rename from MelonLoader/TinyJSON/Types/ProxyNumber.cs rename to MelonLoader/TinyJSON/ProxyNumber.cs diff --git a/MelonLoader/TinyJSON/Types/ProxyObject.cs b/MelonLoader/TinyJSON/ProxyObject.cs similarity index 100% rename from MelonLoader/TinyJSON/Types/ProxyObject.cs rename to MelonLoader/TinyJSON/ProxyObject.cs diff --git a/MelonLoader/TinyJSON/Types/ProxyString.cs b/MelonLoader/TinyJSON/ProxyString.cs similarity index 100% rename from MelonLoader/TinyJSON/Types/ProxyString.cs rename to MelonLoader/TinyJSON/ProxyString.cs diff --git a/MelonLoader/TinyJSON/Types/Variant.cs b/MelonLoader/TinyJSON/Variant.cs similarity index 100% rename from MelonLoader/TinyJSON/Types/Variant.cs rename to MelonLoader/TinyJSON/Variant.cs diff --git a/MelonLoader/Preferences/TomlMapper.cs b/MelonLoader/TomlMapper.cs similarity index 100% rename from MelonLoader/Preferences/TomlMapper.cs rename to MelonLoader/TomlMapper.cs diff --git a/MelonLoader/Attributes/VerifyLoaderBuildAttribute.cs b/MelonLoader/VerifyLoaderBuildAttribute.cs similarity index 100% rename from MelonLoader/Attributes/VerifyLoaderBuildAttribute.cs rename to MelonLoader/VerifyLoaderBuildAttribute.cs diff --git a/MelonLoader/Attributes/VerifyLoaderVersionAttribute.cs b/MelonLoader/VerifyLoaderVersionAttribute.cs similarity index 100% rename from MelonLoader/Attributes/VerifyLoaderVersionAttribute.cs rename to MelonLoader/VerifyLoaderVersionAttribute.cs From 3467c767ad695739ce0fac95fd9db27f515280db Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 19:01:39 +0100 Subject: [PATCH 03/18] Remove MelonStartScreen --- Dependencies/MelonStartScreen/Core.cs | 288 ------ .../MelonStartScreen/MelonStartScreen.csproj | 24 - Dependencies/MelonStartScreen/ModLoadStep.cs | 10 - .../NativeUtils/NativeFieldValueAttribute.cs | 21 - .../NativeUtils/NativeSignatureAttribute.cs | 21 - .../NativeUtils/NativeSignatureFlags.cs | 17 - .../NativeUtils/NativeSignatureResolver.cs | 161 --- .../MelonStartScreen/ProgressParser.cs | 364 ------- .../Resources/Loading_Halloween.dat | Bin 78563 -> 0 bytes .../Resources/Loading_Lemon.dat | Bin 209901 -> 0 bytes .../Resources/Loading_Melon.dat | Bin 180851 -> 0 bytes .../Resources/Logo_Halloween.dat | Bin 58554 -> 0 bytes .../MelonStartScreen/Resources/Logo_Lemon.dat | Bin 42415 -> 0 bytes .../MelonStartScreen/Resources/Logo_Melon.dat | Bin 50537 -> 0 bytes .../MelonStartScreen/ScreenRenderer.cs | 125 --- .../MelonStartScreen/TextMeshGenerator.cs | 51 - .../UI/Objects/UI_AnimatedImage.cs | 90 -- .../UI/Objects/UI_Background.cs | 48 - .../MelonStartScreen/UI/Objects/UI_Image.cs | 65 -- .../MelonStartScreen/UI/Objects/UI_Object.cs | 38 - .../UI/Objects/UI_ProgressBar.cs | 63 -- .../MelonStartScreen/UI/Objects/UI_Text.cs | 98 -- .../UI/StartScreenResources.cs | 34 - .../UI/Themes/UI_Theme_Default.cs | 13 - .../UI/Themes/UI_Theme_Lemon.cs | 13 - .../UI/Themes/UI_Theme_Pumpkin.cs | 46 - Dependencies/MelonStartScreen/UI/UI_Anchor.cs | 20 - Dependencies/MelonStartScreen/UI/UI_Style.cs | 41 - Dependencies/MelonStartScreen/UI/UI_Theme.cs | 283 ----- Dependencies/MelonStartScreen/UI/UI_Utils.cs | 160 --- .../UnhollowerMini/Il2CppException.cs | 35 - .../UnhollowerMini/Il2CppSystem/Byte.cs | 18 - .../UnhollowerMini/Il2CppSystem/Int32.cs | 18 - .../UnhollowerMini/Il2CppSystem/Type.cs | 30 - .../InternalClassPointerStore.cs | 27 - .../UnhollowerMini/InternalObjectBase.cs | 34 - .../UnhollowerMini/InternalType.cs | 22 - .../ObjectCollectedException.cs | 9 - .../UnhollowerMini/UnityInternals.cs | 696 ------------- .../UnityEngine/CoreModule/Color.cs | 36 - .../UnityEngine/CoreModule/Color32.cs | 42 - .../UnityEngine/CoreModule/FilterMode.cs | 9 - .../UnityEngine/CoreModule/GL.cs | 22 - .../UnityEngine/CoreModule/Graphics.cs | 117 --- .../UnityEngine/CoreModule/HideFlags.cs | 15 - .../UnityEngine/CoreModule/ImageConversion.cs | 40 - .../Internal_DrawTextureArguments.cs | 85 -- .../UnityEngine/CoreModule/Material.cs | 27 - .../UnityEngine/CoreModule/Mesh.cs | 168 --- .../UnityEngine/CoreModule/Quaternion.cs | 25 - .../UnityEngine/CoreModule/Rect.cs | 32 - .../UnityEngine/CoreModule/Resources.cs | 41 - .../UnityEngine/CoreModule/Screen.cs | 42 - .../UnityEngine/CoreModule/SystemInfo.cs | 23 - .../UnityEngine/CoreModule/Texture.cs | 45 - .../UnityEngine/CoreModule/Texture2D.cs | 98 -- .../UnityEngine/CoreModule/UnityDebug.cs | 24 - .../UnityEngine/CoreModule/UnityObject.cs | 71 -- .../UnityEngine/CoreModule/Vector2.cs | 25 - .../UnityEngine/CoreModule/Vector3.cs | 40 - .../UnityEngine/CoreModule/Vector4.cs | 25 - .../UnityEngine/CoreModule/VertexAttribute.cs | 22 - .../CoreModule/VerticalWrapMode.cs | 8 - .../UnityEngine/TextRenderingModule/Font.cs | 33 - .../TextRenderingModule/FontStyle.cs | 10 - .../TextRenderingModule/TextAnchor.cs | 15 - .../TextGenerationSettings.cs | 137 --- .../TextRenderingModule/TextGenerator.cs | 63 -- .../TextRenderingModule/UIVertex.cs | 108 -- .../MelonStartScreen/UnityPlayer/GfxDevice.cs | 104 -- .../MelonStartScreen/Windows/DropFile.cs | 15 - Dependencies/MelonStartScreen/Windows/Msg.cs | 16 - .../MelonStartScreen/Windows/Point.cs | 8 - .../MelonStartScreen/Windows/User32.cs | 30 - .../MelonStartScreen/Windows/WindowMessage.cs | 969 ------------------ .../MelonStartScreen/mgGif/Decoder.cs | 628 ------------ Dependencies/MelonStartScreen/mgGif/Image.cs | 40 - .../MelonStartScreen/mgGif/LICENSE.txt | 21 - MelonLoader.sln | 8 - 79 files changed, 6270 deletions(-) delete mode 100644 Dependencies/MelonStartScreen/Core.cs delete mode 100644 Dependencies/MelonStartScreen/MelonStartScreen.csproj delete mode 100644 Dependencies/MelonStartScreen/ModLoadStep.cs delete mode 100644 Dependencies/MelonStartScreen/NativeUtils/NativeFieldValueAttribute.cs delete mode 100644 Dependencies/MelonStartScreen/NativeUtils/NativeSignatureAttribute.cs delete mode 100644 Dependencies/MelonStartScreen/NativeUtils/NativeSignatureFlags.cs delete mode 100644 Dependencies/MelonStartScreen/NativeUtils/NativeSignatureResolver.cs delete mode 100644 Dependencies/MelonStartScreen/ProgressParser.cs delete mode 100644 Dependencies/MelonStartScreen/Resources/Loading_Halloween.dat delete mode 100644 Dependencies/MelonStartScreen/Resources/Loading_Lemon.dat delete mode 100644 Dependencies/MelonStartScreen/Resources/Loading_Melon.dat delete mode 100644 Dependencies/MelonStartScreen/Resources/Logo_Halloween.dat delete mode 100644 Dependencies/MelonStartScreen/Resources/Logo_Lemon.dat delete mode 100644 Dependencies/MelonStartScreen/Resources/Logo_Melon.dat delete mode 100644 Dependencies/MelonStartScreen/ScreenRenderer.cs delete mode 100644 Dependencies/MelonStartScreen/TextMeshGenerator.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Objects/UI_AnimatedImage.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Objects/UI_Background.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Objects/UI_Image.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Objects/UI_Object.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Objects/UI_ProgressBar.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Objects/UI_Text.cs delete mode 100644 Dependencies/MelonStartScreen/UI/StartScreenResources.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Default.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Lemon.cs delete mode 100644 Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Pumpkin.cs delete mode 100644 Dependencies/MelonStartScreen/UI/UI_Anchor.cs delete mode 100644 Dependencies/MelonStartScreen/UI/UI_Style.cs delete mode 100644 Dependencies/MelonStartScreen/UI/UI_Theme.cs delete mode 100644 Dependencies/MelonStartScreen/UI/UI_Utils.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/Il2CppException.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Byte.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Int32.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Type.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/InternalClassPointerStore.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/InternalObjectBase.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/InternalType.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/ObjectCollectedException.cs delete mode 100644 Dependencies/MelonStartScreen/UnhollowerMini/UnityInternals.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color32.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/FilterMode.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/GL.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Graphics.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/HideFlags.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/ImageConversion.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Internal_DrawTextureArguments.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Material.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Mesh.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Quaternion.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Rect.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Resources.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Screen.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/SystemInfo.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture2D.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityDebug.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityObject.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector2.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector3.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector4.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/VertexAttribute.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/CoreModule/VerticalWrapMode.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/Font.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/FontStyle.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextAnchor.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerationSettings.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerator.cs delete mode 100644 Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/UIVertex.cs delete mode 100644 Dependencies/MelonStartScreen/UnityPlayer/GfxDevice.cs delete mode 100644 Dependencies/MelonStartScreen/Windows/DropFile.cs delete mode 100644 Dependencies/MelonStartScreen/Windows/Msg.cs delete mode 100644 Dependencies/MelonStartScreen/Windows/Point.cs delete mode 100644 Dependencies/MelonStartScreen/Windows/User32.cs delete mode 100644 Dependencies/MelonStartScreen/Windows/WindowMessage.cs delete mode 100644 Dependencies/MelonStartScreen/mgGif/Decoder.cs delete mode 100644 Dependencies/MelonStartScreen/mgGif/Image.cs delete mode 100644 Dependencies/MelonStartScreen/mgGif/LICENSE.txt diff --git a/Dependencies/MelonStartScreen/Core.cs b/Dependencies/MelonStartScreen/Core.cs deleted file mode 100644 index 651525d4d..000000000 --- a/Dependencies/MelonStartScreen/Core.cs +++ /dev/null @@ -1,288 +0,0 @@ -using MelonLoader.MelonStartScreen.NativeUtils; -using MelonLoader.Modules; -using MelonLoader.NativeUtils.PEParser; -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; -using Windows; -using MelonLoader.Utils; -using MelonLoader.NativeUtils; - -namespace MelonLoader.MelonStartScreen -{ - internal class Core : MelonModule - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate IntPtr User32SetTimerDelegate(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, IntPtr lpTimerFunc); - private static NativeHook user32Hook = new(); - - private static bool nextSetTimerIsUnity = false; - private static IntPtr titleBarTimer; - - private static bool functionRunDone = false; - private static int functionRunResult = 0; - - internal static string FolderPath; - internal static string ThemesFolderPath; - - public static Core instance; - - public static MelonLogger.Instance Logger => instance.LoggerInstance; - - public override void OnInitialize() - { - instance = this; - } - - private static int LoadAndRun(LemonFunc functionToWaitForAsync) - { - // Start Screen has no signatures for Development Builds of UnityPlayer.dll - if (MelonUnityEngine.UnityDebug.isDebugBuild) - return functionToWaitForAsync(); - - Logger.Msg("Initializing..."); - - FolderPath = Path.Combine(MelonEnvironment.UserDataDirectory, "MelonStartScreen"); - if (!Directory.Exists(FolderPath)) - Directory.CreateDirectory(FolderPath); - - ThemesFolderPath = Path.Combine(FolderPath, "Themes"); - if (!Directory.Exists(ThemesFolderPath)) - Directory.CreateDirectory(ThemesFolderPath); - - UI_Theme.Load(); - if (!UI_Theme.General.Enabled) - return functionToWaitForAsync(); - - // We try to resolve all the signatures, which are available for Unity 2018.1.0+ - // If we can't find them (signatures changed or <2018.1.0), then we run the function and return. - try - { - if (!NativeSignatureResolver.Apply()) - return functionToWaitForAsync(); - - if (!ApplyUser32SetTimerPatch()) - return functionToWaitForAsync(); - - MelonDebug.Msg("Initializing Screen Renderer"); - ScreenRenderer.Init(); - MelonDebug.Msg("Screen Renderer initialized"); - - RegisterMessageCallbacks(); - - // Initial render - ScreenRenderer.Render(); - } - catch (Exception e) - { - Logger.Error(e); - ScreenRenderer.disabled = true; - return functionToWaitForAsync(); - } - - SubscribeToCoreCallbacks(); - - StartFunction(functionToWaitForAsync); - MainLoop(); - - return functionRunResult; - } - - private static void SubscribeToCoreCallbacks() - { - MelonEvents.OnApplicationLateStart.Subscribe(Finish, int.MinValue); - MelonEvents.OnApplicationStart.Subscribe(OnApplicationStart, int.MaxValue); - MelonBase.OnMelonInitializing.Subscribe(OnMelonInitializing, 100); - MelonAssembly.OnAssemblyResolving.Subscribe(OnMelonsResolving, 100); - } - - private static void RegisterMessageCallbacks() - { - MelonLogger.MsgDrawingCallbackHandler += (namesection_color, txt_color, namesection, txt) => ScreenRenderer.UpdateProgressFromLog(txt); - MelonDebug.MsgCallbackHandler += (txt_color, txt) => ScreenRenderer.UpdateProgressFromLog(txt); - } - - #region User32::SetTime Path - - private static unsafe bool ApplyUser32SetTimerPatch() - { - IntPtr original = PEUtils.GetExportedFunctionPointerForModule("USER32.dll", "SetTimer"); - MelonDebug.Msg($"User32::SetTimer original: 0x{(long)original:X}"); - - if (original == IntPtr.Zero) - { - MelonDebug.Error("Failed to find USER32.dll::SetTimer"); - return false; - } - - // We get a native function pointer to User32SetTimerDetour from our current class -#if NET6_0_OR_GREATER - delegate* unmanaged[Cdecl] detourPtr = &User32SetTimerDetour; -#else - IntPtr detourPtr = Marshal.GetFunctionPointerForDelegate((User32SetTimerDelegate)User32SetTimerDetour); - - if (detourPtr == IntPtr.Zero) - { - MelonDebug.Error("Failed to find User32SetTimerDetour"); - return false; - } -#endif - user32Hook.Target = original; - user32Hook.Detour = (IntPtr)detourPtr; - - // And we patch SetTimer to replace it by our hook - MelonDebug.Msg($"Applying USER32.dll::SetTimer Hook at 0x{original.ToInt64():X}"); - user32Hook.Attach(); - MelonDebug.Msg("Applied USER32.dll::SetTimer patch"); - - return true; - } - -#if NET6_0_OR_GREATER - [UnmanagedCallersOnly(CallConvs = new[] {typeof(CallConvCdecl)})] -#endif - private unsafe static IntPtr User32SetTimerDetour(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, IntPtr timerProc) - { - if (nextSetTimerIsUnity) - { - nextSetTimerIsUnity = false; - return IntPtr.Zero; - } - - return user32Hook.Trampoline(hWnd, nIDEvent, uElapse, timerProc); - } - - #endregion - - private static void StartFunction(LemonFunc func) - { - new Thread(() => - { - functionRunResult = func(); - functionRunDone = true; - }) - { - IsBackground = true, - Name = "MelonStartScreen Function Thread" - }.Start(); - } - - private static void MainLoop() - { - while (!functionRunDone) // WM_QUIT - { - ProcessEventsAndRender(true); - } - - if (titleBarTimer != IntPtr.Zero) - { - User32.KillTimer(IntPtr.Zero, titleBarTimer); - titleBarTimer = IntPtr.Zero; - } - } - - #region Event Processing and Rendering - - private static void ProcessEventsAndRender(bool isMainLoop = false) - { - ProcessMessages(); - - if (titleBarTimer != IntPtr.Zero) - { - User32.KillTimer(IntPtr.Zero, titleBarTimer); - titleBarTimer = IntPtr.Zero; - } - ScreenRenderer.Render(); - if (isMainLoop) - Thread.Sleep(16); // ~60fps - else - { - if (titleBarTimer != IntPtr.Zero) - { - User32.KillTimer(IntPtr.Zero, titleBarTimer); - titleBarTimer = IntPtr.Zero; - } - } - } - - private static void ProcessMessages() - { - while (true) - { - User32.PeekMessage(out Msg msg, IntPtr.Zero, 0, 0, 0); - if (msg.message == WindowMessage.QUIT) - { - Process.GetCurrentProcess().Kill(); - return; - } - else if (User32.PeekMessage(out msg, IntPtr.Zero, 0, 0, 1)) // If there a message pending - { - if (msg.message == WindowMessage.NCLBUTTONDOWN || msg.message == (WindowMessage)0x242 /* NCPOINTERDOWN */) - { - if (titleBarTimer == IntPtr.Zero) - titleBarTimer = User32.SetTimer(IntPtr.Zero, IntPtr.Zero, 10, TitleBarTimerUpdateCallback); - nextSetTimerIsUnity = true; - } - else if (msg.message == WindowMessage.PAINT) - ScreenRenderer.Render(); - - User32.TranslateMessage(ref msg); - User32.DispatchMessage(ref msg); - } - else - return; - } - } - - private static void TitleBarTimerUpdateCallback(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime) - { - ScreenRenderer.Render(); - } - - #endregion - - #region Calls from MelonLoader - - internal static void OnMelonInitializing(MelonBase melon) - { - ScreenRenderer.UpdateProgressState(ModLoadStep.InitializeMelons); - ScreenRenderer.UpdateProgressFromMod(melon); - ProcessEventsAndRender(); - } - - internal static void OnMelonsResolving(Assembly asm) - { - ScreenRenderer.UpdateProgressState(ModLoadStep.LoadMelons); - ScreenRenderer.UpdateProgressFromModAssembly(asm); - ProcessEventsAndRender(); - } - - internal static void OnApplicationStart() - { - ScreenRenderer.UpdateProgressState(ModLoadStep.OnApplicationStart); - ProcessEventsAndRender(); - } - - internal static void DisplayModLoadIssuesIfNeeded() - { - // TODO Start a new locking thread with a display of the issues and buttons to either close the game or continue - } - - internal static void Finish() - { - ScreenRenderer.UpdateMainProgress("Starting game...", 1f); - ScreenRenderer.Render(); // Final render, to set the progress bar to 100% - - MelonEvents.OnApplicationLateStart.Unsubscribe(typeof(Core).GetMethod(nameof(Finish), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); - MelonEvents.OnApplicationStart.Unsubscribe(typeof(Core).GetMethod(nameof(OnApplicationStart), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); - MelonBase.OnMelonInitializing.Unsubscribe(typeof(Core).GetMethod(nameof(OnMelonInitializing), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); - MelonAssembly.OnAssemblyResolving.Unsubscribe(typeof(Core).GetMethod(nameof(OnMelonsResolving), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); - } - - #endregion - } -} diff --git a/Dependencies/MelonStartScreen/MelonStartScreen.csproj b/Dependencies/MelonStartScreen/MelonStartScreen.csproj deleted file mode 100644 index 605a76a07..000000000 --- a/Dependencies/MelonStartScreen/MelonStartScreen.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - MelonLoader.MelonStartScreen - net35;net6 - true - $(MLOutDir)/MelonLoader - true - true - embedded - - - - - - - - - - - - Runtime - - - \ No newline at end of file diff --git a/Dependencies/MelonStartScreen/ModLoadStep.cs b/Dependencies/MelonStartScreen/ModLoadStep.cs deleted file mode 100644 index d29ccf30f..000000000 --- a/Dependencies/MelonStartScreen/ModLoadStep.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MelonLoader.MelonStartScreen -{ - internal enum ModLoadStep - { - Generation, - LoadMelons, - InitializeMelons, - OnApplicationStart - } -} diff --git a/Dependencies/MelonStartScreen/NativeUtils/NativeFieldValueAttribute.cs b/Dependencies/MelonStartScreen/NativeUtils/NativeFieldValueAttribute.cs deleted file mode 100644 index c9a847e13..000000000 --- a/Dependencies/MelonStartScreen/NativeUtils/NativeFieldValueAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace MelonLoader.MelonStartScreen.NativeUtils -{ - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - class NativeFieldValueAttribute : Attribute - { - internal uint LookupIndex { get; } - internal NativeSignatureFlags Flags { get; } - internal object Value { get; } - internal string[] MinimalUnityVersions { get; } - - public NativeFieldValueAttribute(uint lookupIndex, NativeSignatureFlags flags, object value, params string[] minimalUnityVersions) - { - LookupIndex = lookupIndex; - Flags = flags; - Value = value; - MinimalUnityVersions = minimalUnityVersions; - } - } -} diff --git a/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureAttribute.cs b/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureAttribute.cs deleted file mode 100644 index cb048ac7b..000000000 --- a/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace MelonLoader.MelonStartScreen.NativeUtils -{ - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - internal class NativeSignatureAttribute : Attribute - { - internal uint LookupIndex { get; } - internal NativeSignatureFlags Flags { get; } - internal string Signature { get; } - internal string[] MinimalUnityVersions { get; } - - internal NativeSignatureAttribute(uint lookupIndex, NativeSignatureFlags flags, string signature, params string[] minimalUnityVersions) - { - LookupIndex = lookupIndex; - Flags = flags; - Signature = signature; - MinimalUnityVersions = minimalUnityVersions; - } - } -} diff --git a/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureFlags.cs b/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureFlags.cs deleted file mode 100644 index 8ec8628ae..000000000 --- a/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureFlags.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MelonLoader.MelonStartScreen.NativeUtils -{ - internal enum NativeSignatureFlags - { - None = 0, - - Il2Cpp = 1, - Mono = 2, - - //Dev = 4, - //NonDev = 8 - - X86 = 16, - X64 = 32, - //ARMEABIV7A = 64, - } -} diff --git a/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureResolver.cs b/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureResolver.cs deleted file mode 100644 index 94b626a6a..000000000 --- a/Dependencies/MelonStartScreen/NativeUtils/NativeSignatureResolver.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using MelonLoader.NativeUtils; - -namespace MelonLoader.MelonStartScreen.NativeUtils -{ - internal static class NativeSignatureResolver - { - internal static bool Apply() - { - NativeSignatureFlags currentFlags = default; - currentFlags |= MelonUtils.IsGame32Bit() ? NativeSignatureFlags.X86 : NativeSignatureFlags.X64; - currentFlags |= MelonUtils.IsGameIl2Cpp() ? NativeSignatureFlags.Il2Cpp : NativeSignatureFlags.Mono; - MelonDebug.Msg("Current Unity flags: " + (uint)currentFlags); - - string currentUnityVersion = InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); - string moduleName = "UnityPlayer.dll"; -#if LINUX - // TODO -#elif OSX - // TODO -#elif ANDROID - // TODO -#else - if (!currentUnityVersion.StartsWith("20") || currentUnityVersion.StartsWith("2017.1")) - moduleName = "player_win.exe"; -#endif - - - IntPtr moduleAddress = IntPtr.Zero; - int moduleSize = 0; - - foreach (ProcessModule module in Process.GetCurrentProcess().Modules) - { - if (module.ModuleName == moduleName) - { - moduleAddress = module.BaseAddress; - moduleSize = module.ModuleMemorySize; - break; - } - } - - if (moduleAddress == IntPtr.Zero) - { - Core.Logger.Error($"Failed to find module \"{moduleName}\""); - return false; - } - - bool success = true; - foreach (Type type in typeof(NativeSignatureResolver).Assembly.GetTypes()) - { - foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) - { - bool hasAttribute = false; - bool signaturefound = false; - bool optionalOnEarlyVersion = false; - IOrderedEnumerable nativeSignatureAttributes = fi.GetCustomAttributes(false) - .Where(attr => attr is NativeSignatureAttribute) - .Select(attr => (NativeSignatureAttribute)attr) - .OrderByDescending(attr => attr.LookupIndex); - foreach (NativeSignatureAttribute attribute in nativeSignatureAttributes) - { - hasAttribute = true; - if ((attribute.Flags & currentFlags) != attribute.Flags) - continue; - - if (!IsUnityVersionOverOrEqual(currentUnityVersion, attribute.MinimalUnityVersions)) - continue; - - signaturefound = true; - - if (attribute.Signature == null) - { - optionalOnEarlyVersion = true; - break; - } - - IntPtr ptr = CppUtils.Sigscan(moduleAddress, moduleSize, attribute.Signature); - if (ptr == IntPtr.Zero) - { - success = false; - Core.Logger.Error("Failed to find the signature for field " + fi.Name + " in module. Signature: " + attribute.Signature); - break; - } - - if (typeof(Delegate).IsAssignableFrom(fi.FieldType)) - fi.SetValue(null, Marshal.GetDelegateForFunctionPointer(ptr, fi.FieldType)); - else if (typeof(IntPtr).IsAssignableFrom(fi.FieldType)) - fi.SetValue(null, ptr); - else - Core.Logger.Error($"Invalid target type for field \"{fi.FieldType} {fi.Name}\""); - - MelonDebug.Msg("Signature for " + fi.Name + ": " + attribute.Signature); - break; - } - - if (hasAttribute && !signaturefound && !optionalOnEarlyVersion) - { - Core.Logger.Error("Failed to find a signature for field " + fi.Name + " for this version of Unity"); - success = false; - } - - hasAttribute = false; - signaturefound = false; - IOrderedEnumerable nativeFieldValueAttributes = fi.GetCustomAttributes(false) - .Where(attr => attr is NativeFieldValueAttribute) - .Select(attr => (NativeFieldValueAttribute)attr) - .OrderByDescending(attr => attr.LookupIndex); - foreach (NativeFieldValueAttribute attribute in nativeFieldValueAttributes) - { - hasAttribute = true; - if ((attribute.Flags & currentFlags) != attribute.Flags) - continue; - - if (!IsUnityVersionOverOrEqual(currentUnityVersion, attribute.MinimalUnityVersions)) - continue; - - signaturefound = true; - - fi.SetValue(null, attribute.Value); - - MelonDebug.Msg("Value for " + fi.Name + ": " + attribute.Value); - break; - } - - if (hasAttribute && !signaturefound) - { - Core.Logger.Error("Failed to find a value for field " + fi.Name + " for this version of Unity"); - success = false; - } - } - } - - return success; - } - - internal static bool IsUnityVersionOverOrEqual(string currentversion, string[] validversions) - { - if (validversions == null || validversions.Length == 0) - return true; - - string[] versionparts = currentversion.Split('.'); - - foreach (string validversion in validversions) - { - string[] validversionparts = validversion.Split('.'); - - if ( - int.Parse(versionparts[0]) >= int.Parse(validversionparts[0]) && - int.Parse(versionparts[1]) >= int.Parse(validversionparts[1]) && - int.Parse(versionparts[2]) >= int.Parse(validversionparts[2])) - return true; - } - - return false; - } - } -} diff --git a/Dependencies/MelonStartScreen/ProgressParser.cs b/Dependencies/MelonStartScreen/ProgressParser.cs deleted file mode 100644 index 2bbf33c72..000000000 --- a/Dependencies/MelonStartScreen/ProgressParser.cs +++ /dev/null @@ -1,364 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Text.RegularExpressions; - -namespace MelonLoader.MelonStartScreen -{ - internal static class ProgressParser - { - private static float generationPercent = MelonUtils.IsGameIl2Cpp() ? 80f : 10f; - private static ModLoadStep currentStep = ModLoadStep.Generation; - private static string currentStepName = "___"; - - internal struct AverageStepDuration - { - public string message; - public float weight; - public string progresstext; - - public AverageStepDuration(string message, float weight, string progresstext) - { - this.message = message; - this.weight = weight; - this.progresstext = progresstext; - } - } - - private static readonly Dictionary stepsNames = new Dictionary() - { - { ModLoadStep.LoadMelons, "Loading Melons" }, - { ModLoadStep.InitializeMelons, "Initializing" }, - { ModLoadStep.OnApplicationStart, "Loading..." } - }; - - public static float GetProgressFromLog(string data, ref string progressText, float default_) // TODO flags (il2cpp/mono, cpp2il/il2cppdumper) - { - if (currentStep != ModLoadStep.Generation) - return default_; - - if (Regex.IsMatch(data, "Assembly is up to date")) - { - generationPercent = default_ * 100f; - return default_; - } - - float totalTime = 0; - float progressTime = 0; - foreach (var entry in averageStepDurations) - { - if (progressTime <= 0f && Regex.IsMatch(data, entry.message)) - { - progressTime = totalTime; - progressText = entry.progresstext ?? data; - } - - totalTime += entry.weight; - } - - return progressTime > 0 ? (progressTime / totalTime) * (generationPercent * 0.01f) : default_; - } - - public static float GetProgressFromMod(MelonBase melon, ref string progressText) - { - progressText = $"{currentStepName} {melon.MelonTypeName}: {melon.Info.Name} {melon.Info.Version}"; - - float generationPart = generationPercent * 0.01f; - return generationPart + (((int)currentStep - 1) * ((1 - generationPart) / 4)); - } - - public static float GetProgressFromModAssembly(Assembly asm, ref string progressText) - { - progressText = $"{currentStepName}: {Path.GetFileName(asm.Location)}"; - - float generationPart = generationPercent * 0.01f; - return generationPart + (((int)currentStep - 1) * ((1 - generationPart) / 4)); - } - - public static bool SetModState(ModLoadStep step, ref string progressText, out float generationPart) - { - generationPart = generationPercent * 0.01f; - generationPart += ((int)step - 1) * ((1 - generationPart) / 3); - - if (currentStep == step) - return false; - - currentStep = step; - if (!stepsNames.TryGetValue(step, out currentStepName)) - currentStepName = $"{step}"; - progressText = currentStepName; - - return true; - } - - internal static readonly AverageStepDuration[] averageStepDurations = new AverageStepDuration[] - { - // Il2CppAssemblyGenerator - new AverageStepDuration( - @"Contacting RemoteAPI\.\.\.", - 100f, - @"Initialization - Contacting Remote API" - ), - new AverageStepDuration( - @"Downloading Unity \S+ Dependencies\.\.\.", - 1000f, - @"Initialization - Downloading Unity Dependencies" - ), - new AverageStepDuration( - @"Extracting .* to .*UnityDpendencies", - 500f, - @"Initialization - Extracting Unity Dependencies" - ), - new AverageStepDuration( - @"Downloading Cpp2IL\.\.\.", - 500f, - @"Initialization - Downloading Cpp2IL" - ), - new AverageStepDuration( - @"Extracting .* to .*Cpp2IL", - 500f, - @"Initialization - Extracting Cpp2IL" - ), - new AverageStepDuration( - @"Downloading DeobfuscationMap\.\.\.", - 500f, - @"Initialization - Downloading DeobfuscationMap" - ), - new AverageStepDuration( - @"Checking GameAssembly\.\.\.", - 1000f, - @"Initialization - Checking GameAssembly" - ), - - - // Cpp2IL - // Slaynash: I skipped a lot of steps taking less than 100ms, but we may want to add them back for slower platforms - new AverageStepDuration( - @"Initializing metadata\.\.\.", - 2500f, - "Cpp2IL - Initializing metadata" - ), - new AverageStepDuration( - @"Searching Binary for Required Data", - 402f, - "Cpp2IL - Seaching Binary for Required Data" - ), - new AverageStepDuration( - @"Initializing Binary", - 402f, - "Cpp2IL - Initializing Binary" - ), - new AverageStepDuration( - @"Initializing Binary", - 2533f, - "Cpp2IL - Initializing Binary" - ), - new AverageStepDuration( - @"Pre-generating stubs", - 708f, - "Cpp2IL - Building Assemblies: Pre-generating stubs" - ), - new AverageStepDuration( - @"Populating mscorlib\.dll", - 340f, - "Cpp2IL - Building Assemblies: Populating assemblies" - ), - new AverageStepDuration( - @"Populating Assembly-CSharp\.dll", - 3000f, - "Cpp2IL - Building Assemblies: Populating assemblies" - ), - new AverageStepDuration( - @"Fixing up explicit overrides", - 1000f, - "Cpp2IL - Fixing up explicit overrides" - ), - new AverageStepDuration( - @"Running Scan for Known Functions", - 300f, - "Cpp2IL - Running Scan for Known Functions" - ), - new AverageStepDuration( - @"Applying type, method, and field attributes", - 5500f, - "Cpp2IL - Applying type, method, and field attributes" - ), - new AverageStepDuration( - @"Saving .* assemblies to ", - 4000f, - "Cpp2IL - Saving assemblies" - ), - - - // Il2CppInterop - new AverageStepDuration( - @"Reading assemblies\.\.\.", - 170f, - "Il2CppInterop - Reading assemblies" - ), - new AverageStepDuration( - @"Reading system assemblies\.\.\.", - 14f, - "Il2CppInterop - Reading system assemblies" - ), - new AverageStepDuration( - @"Reading unity assemblies\.\.\.", - 29f, - "Il2CppInterop - Reading unity assemblies" - ), - new AverageStepDuration( - @"Creating rewrite assemblies\.\.\.", - 20f, - "Il2CppInterop - Creating rewrite assemblies" - ), - new AverageStepDuration( - @"Computing renames\.\.\.", - 281f, - "Il2CppInterop - Computing renames" - ), - new AverageStepDuration( - @"Creating typedefs\.\.\.", - 109f, - "Il2CppInterop - Creating typedefs" - ), - new AverageStepDuration( - @"Computing struct blittability\.\.\.", - 10f, - "Il2CppInterop - Computing struct blittability" - ), - new AverageStepDuration( - @"Filling typedefs\.\.\.", - 27f, - "Il2CppInterop - Filling typedefs" - ), - new AverageStepDuration( - @"Filling generic constraints\.\.\.", - 6f, - "Il2CppInterop - Filling generic constraints" - ), - new AverageStepDuration( - @"Creating members\.\.\.", - 2256f, - "Il2CppInterop - Creating members" - ), - new AverageStepDuration( - @"Scanning method cross-references\.\.\.", - 1919f, - "Il2CppInterop - Scanning method cross-references" - ), - new AverageStepDuration( - @"Finalizing method declarations\.\.\.", - 2867f, - "Il2CppInterop - Finalizing method declarations" - ), - new AverageStepDuration( - @"Filling method parameters\.\.\.", - 510f, - "Il2CppInterop - Filling method parameters" - ), - new AverageStepDuration( - @"Creating static constructors\.\.\.", - 1237f, - "Il2CppInterop - Creating static constructors" - ), - new AverageStepDuration( - @"Creating value type fields\.\.\.", - 186f, - "Il2CppInterop - Creating value type fields" - ), - new AverageStepDuration( - @"Creating enums\.\.\.", - 69f, - "Il2CppInterop - Creating enums" - ), - new AverageStepDuration( - @"Creating IntPtr constructors\.\.\.", - 63f, - "Il2CppInterop - Creating IntPtr constructors" - ), - new AverageStepDuration( - @"Creating type getters\.\.\.", - 132f, - "Il2CppInterop - Creating type getters" - ), - /* - ( - @"Creating non-blittable struct constructors\.\.\.", - 38f, - "Il2CppInterop - Creating non-blittable struct constructors" - ), - ( - @"Creating generic method static constructors\.\.\.", - 42f, - "Il2CppInterop - Creating generic method static constructors" - ), - */ - new AverageStepDuration( - @"Creating field accessors\.\.\.", - 1642f, - "Il2CppInterop - Creating field accessors" - ), - new AverageStepDuration( - @"Filling methods\.\.\.", - 2385f, - "Il2CppInterop - Filling methods" - ), - new AverageStepDuration( - @"Generating implicit conversions\.\.\.", - 121f, - "Il2CppInterop - Generating implicit conversions" - ), - new AverageStepDuration( - @"Creating properties\.\.\.", - 102f, - "Il2CppInterop - Creating properties" - ), - new AverageStepDuration( - @"Unstripping types\.\.\.", - 44f, - "Il2CppInterop - Unstripping types" - ), - new AverageStepDuration( - @"Unstripping fields\.\.\.", - 5f, - "Il2CppInterop - Unstripping fields" - ), - new AverageStepDuration( - @"Unstripping methods\.\.\.", - 241f, - "Il2CppInterop - Unstripping methods" - ), - new AverageStepDuration( - @"Unstripping method bodies\.\.\.", - 266f, - "Il2CppInterop - Unstripping method bodies" - ), - new AverageStepDuration( - @"Generating forwarded types\.\.\.", - 4f, - "Il2CppInterop - Generating forwarded types" - ), - new AverageStepDuration( - @"Writing xref cache\.\.\.", - 1179f, - "Il2CppInterop - Writing xref cache" - ), - new AverageStepDuration( - @"Writing assemblies\.\.\.", - 2586f, - "Il2CppInterop - Writing assemblies" - ), - new AverageStepDuration( - @"Writing method pointer map\.\.\.", - 89f, - "Il2CppInterop - Writing method pointer map" - ), - // Move files - new AverageStepDuration( - @"Deleting .*\.dll", - 500f, - "Il2CppInterop - Moving files" - ), - }; - } -} diff --git a/Dependencies/MelonStartScreen/Resources/Loading_Halloween.dat b/Dependencies/MelonStartScreen/Resources/Loading_Halloween.dat deleted file mode 100644 index c7643a77633777ed2bbce233bc1f80ecb00544ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78563 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 z 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 GCX?13Z=F~5hv|J7J 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 diff --git a/Dependencies/MelonStartScreen/Resources/Loading_Lemon.dat b/Dependencies/MelonStartScreen/Resources/Loading_Lemon.dat deleted file mode 100644 index 54c705ddedb58c192496a82e5afc73c39beb7713..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 209901 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 F6i>NTE&!)5JZ0A1O{od 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 BvV{Nu diff --git a/Dependencies/MelonStartScreen/Resources/Logo_Halloween.dat b/Dependencies/MelonStartScreen/Resources/Logo_Halloween.dat deleted file mode 100644 index a34ba5c48632d653d8bee5bfbc59ba54f727e9b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58554 zcmeFXWl$WTs;S7LArm1(K|!I(%SmZKLBXIwK|xm`!b3*T1K}>9 zpon|@wDdeQK;9HCZq8P=4we)izAly&mOi#tP*6TAWqGz=G@ulK=GU?D#Rh-oW0wqs2tFwxC}4GA50@^C@8R z*8BX|pFh7=UI+EuS1hfQn2O5u()U(OvHSfAz}T!0Ha$G{&wkK)bSdiX4FUx}j*PuK zYkcx3swbwcW$48XV&m>*OW*m1zkLkzvzgUY);V}BEdPu&DDZl!PbIkQ=IiaPbkO73 zTFeGpcQ#mF_4wwX!CG(FM41TXt_b4vHfi@crtzNHWP8Ie&=e}zM;6f?7+15 zXzJ_z<57h}?Qh(Ycg-2QONO(m;&r0EPnVwfL@W-yTbpcHV`P-&;$IN}E1y zbo~zWx5DSrl`!t(z3v&g#XjS5Dl+sQoqg~8VAw9@9}FKXsNFNRax+03tV2MVT1V`* z42mND^1F8s94kPEPb@mz4)$Zencs`_^7Ao(?%N9;obHn z-+QYT=+ao&c~=p1Hf2r@Ap{;8en8@S3W*bi4b^75qnSX%`Ls^=tz;(;q6#q{R zJHC!Q=Z@sqPl<7?~ml#l~pfJkdW+Pc%V zT9^F_TjmX-&V$48$9(z$(Na+|oRP~ID0>jjyP9{{wQauf%Q>8VU(4Kxpc%sVKBUU* z^U=>CU4-o>=I&zak6<~~dEN>bwhBu;!rnv$rM{av>YFCJ*bI955&7|ZpzOGCR{Nqx zb%Eg*8+x1~%MuKB*Ah+5)6um=fi^F>s#9t zoo4?Uwl_@++oyH#b{LPj*LpT(UWkI4PuHgwa1z{kOM^LupE5XBdo~EsDc@E_OSdR^ z1zI1-(U5QAxG2Van#wy|$cJVf8>e3=htihf z@*3qXB}<`fIj18L3FTd#LF*E`cYj6Tg2t@r9mmLnwJ(bDCVV2g2( zMfN-8b(km2I$0ybEd(6zx;|yi+7Roz^$1|1c+duMS?v;aS6|@~cN}w%>(5(^<7ad^ z=ClfZBn$~$!(O&`oN8gG*o`4HF#AB(!Fi`Sz{tUqW`^U=9F)E;I%C_#dhTrkUhGn_CpZ*N!R}Wkhp@RZMo5(^MFWz7^{8H5n)G~}yja*2CI@4G$Oy2UD!uL& z?6kh-#@>@8@;9_$ge|=No&3-htS%NA1dEi2Tt7;gWv2;XCtzd=rhE_m=HfNK_4QX> z*5O5l>bM??^7y8oBQe46jFedCKJS~$G3`;bro@g=j@gnp=x!iZ_uPmWrpvnHs{1*6 zr$Va%Tu{VaiFiEQ+;?diiq~MF>*h`wwT?wPxffhIXX;#6apz3TrK^WZY6?$qZYzJQ zh17+l({Ax>#`p-GA&IeWur737Nkt-c2j)j*Fo$eW7jc{*HeDOm@?13si z*8T=InICpP5e{&CpkB|q0qf!?^i(%vWjjmGG5tuGm|>D_g$Ba(i-j29xG|dazkSD* z8*U=W&?XgsAxXttq*9!0^cq_j$ufE|mRn1;Q41A9?+QTNKa|4Nz8bZn6`J51JpUE2)l?xX%c@gjO>6VBFNs z&1O*W7#bZY12~i>6&iPhh`LeAO(bAe_KJKG@NU*bcKMFwaV&_o>0%RF_qRk&>no~> z3n`gh5MXr0u8T>xQ+o=q%MwG-7JtEwg&}X< z zao(TM-S(~xIJvBTlIUBghYabW()P-z!`ocY4r~E-90;GS?^6@6PmCRF0 zj9L-E!Jiavv2HN8%!Ri?=mWBSWgT~9Z{iec7e>S%7IlUTXl<2TKpqi!8-kWIIlG+B zi5!{$TNfk8lFcYPE>|2eD1&8|5i?oDUt35b*824hR)HwIrLch1&z>96P^_--17+}d zg$iF~V)^O!$?;iH7**t|L zjyC2P;ZgWBJ@lvi+@k9dMKf7C)06sU zXgyROZ>k!}NY8YE?RowKztJZ4)HN@>d0iA-p5Uo0(Ih}K9SS9*Pw zIP8>Uf0$f?L~jFMOgp;UB|4Ap=dn!UjYw?D#`V|@2NJp zlQI1fDSAP=*f)yNW5i)o{B5zRxq5soBnqK;?e{sk!~~vqnbYrW-^j|5w%kF(Mj_*T z5CH0xX{=nJ%d(Vk$vS^4V#*EX zvZHi;G4oHh6fmF^o~Ju}a_11{j`&1!Y?P@~`B2XfJEQ-@v~KiUzptU_az3^&iP}IX zN>w?ihH9yG*FTDD__s1G*Wa>1hVvXQ`ZiiE5sk1cW-AjYRf3BsiOCZsq7*#YNwv7r z7>}HrSy#JPzEG8jmgkWSrUFyhV5J6=s023 zRW8?JMd0nrk~YPo3-T9Pn!{lL6lKrYOxIj7xI;+DISET)9z*L{TW8ehZ60sR5&v|G z>&T%Xr#x`?zOIyZ`~%Jma|-LKy6O@d?@~&)qs7u{=xT>Kp}mY)m~CJ{EhD!7%ps8k6-2 z>harR#uSTBH4W$K==nkxCXGnhM7bh|W3p)hADTVr78gtHZsK4O9=^{DluGFzvLN;T zOhF|ggL|WKJ9x1S@0lm!<6&bVUms#wDyuF-XrxMLYfc~>_Ff#&U(1E>af#r^$jd?e znC*a{AY)U@e3B^i?q`l}P(NOG)qO0hfBHuG5|9F5iw>)%;9*%h$fomVBT71@%6z70 z(Y3{v^7FQy7FK*!9yte;0|~7`W0p3OSR>VT2x{GcikR;A?M?-pl2Uj$=}cr~EZ<1) z#Nuy!uk>T4JU#BkJyWG&xl*XE07^dY5qeQV^4VS=qL}T-uLziuiE590c~fvfV2U=< zcVcS3dRun)^sRBSb5%AZ+LF8lxk4JS954s;Y!>dBS^xvL(dz!VQViL!sHwp>?ytm% zOi0HoWD!O+P~kElRy3e1hiOOxru``~^or1M1o-txa-rr@ z*rw}?zMx$lT&8dtq^X{}Dr?2d@ktt2dyy~cN+KAsJr=WP4^p0UC{$6)M+9CD;1@s@ zQzD?Us0C<@1`cgSWX&oRNepf^!HY6_(2NMq5WdHg9Qj>xkhSc%d9mGAD8^tvTbZ+v zl3o&OHC+ftCD{Yu-sfFTHrm?}TP|ey1l4Sq+26%&kp**PSlj6=?M>CVZ35M9Yh}eC zhiq#@t=E(Ix)`ZSO^|m=6;#r{_$xD3qS><`G@=}6jiILx!!%RRrTM-9k!60&@~D#b zH59#@`1FL~wHfo4r>(eHD05Z34>dZ`q_W*inwiejJ?-i`LoljjBvSnpKU{m5l^Awd z3Eit29-@Dx!FNM8qqj6uyCgd&|seUaTLTC4mR`=x>h zPp<6eD;)0`XR8EY4Kjg*4(@*a>K%{bj5_q!y|9P0VF4|)NE^WqgrvPwa)|O6yg4(K zxzwJLiv4F!J`VCe8hy4u%tUFv^Tavnz3Nd9A5cC%)cZ5W1x~>dGG#&_QgoO%9_Vg} zN0yuUeB$gNOzE-7IacY6;510+kEUIZ#84K}V54WOq|(F58GRb94UILtG^;)>Jxd@u z!&X(u-(Kke&KRKlfFmq0sy_qpkbg+{)2}1UDoi`1%Bu0hINX^B2L9rHmEsD%?fn2W z9#eXV{Q8jzqg=3E=n!9+y zFbil1Z&fNyesXDnra9nx_oz7h#Vd+!LSG-Lh>74(Vk0xM*r0sH6?KnZ=rq)Hf13!B zW$SlrR3qi1a#4|N>^o8u^9<2pU?dH^D6vYp=oLp#_?=`!GO13V*K8Mi5J|+*Xe2N@ zqO3t@cQx1WUTz{+JuUK7#S?}6~QOq1-o}o=GOlKsY+P4)^O(%MT z-uB%gQT6uXyAHsk${mu)R?;YGIaKA=7@hX;#BJ12v2cq?S+fXTj17#G2SKr}HzEq1 zvkD%+bBa1uA{|bKPH|n2EfpAaG2bTTF+2?LIJ}=LOp?e0iz}oEbcJhV(tJ#x5dR3w zLbqn%n}cQ~fbUX#kQ=o?WW&&xX)q~~4n%%izRIy~9ya%lU}2}Yd?~9SIx_m48Ory& zONPhX^eFw-j4q9$3<=+B`o!wV_vPyEi(ux_DZ8QnK$*|IG2UekNP{Q5Ld*ZL$p~#L zbB{sVM;nm$@SbuAbr<@EX7ddXPdAWbA7Cw&AANPJg=85Q!fJ-B4Yzj9SF=O5I>i?f zVk-s>h4LpJT;1@tTLuA8jBZ_}C5Fbzzd(V~YPG|E0ffWaM69xRi+>f0Ov$Wv;owbI zrWe0mi(DigyQ(Y84!xfBanTk(1$Atwje|j^4sE-C@tkj#487ajp0Fa@%&w^{CqvD< z_<^edpt+5-m8s`yUA!;<%@%xeag8SvZxgnqW-nn}9Caa={gs3R_vC|8;wGINcS}$q zNpPs>Y$VlDg(nN=NX2M!O5H2{w1wZ_G1qMI^e|c|+7BB=u3+-A<@UrT!l1Osc}%|% z%a!1ZWl~2sCvn-i+8)RtB=Vxy-6&{Ky&p!f!(uW+o~-@I=d=ExiK$@}b5FZ zX+(Nl_`7nJKVgc30+VxkWk#Pr60o@G&EV`NO0AtgrlLQ&?m3TFO1UrGDfo|=dVY{bDz{^jtaZwv;`_BC;R_QNlrAPgnlT%jc^GicRb(9R zVcNLdJRh0YL}>H-S>(aWNSS`_a$=P!=)w#%13JAQMxQrJ16#IGow;8l>^KUuDnXf!ZR_mTHcF1~)@1P|5s2>pEAh!3~VNl}q>L%YW14 z*!e4=IMg+i1cfi-0`Trlx<5_4thLajuVm<)h4wd-T*e?*j@GZ6j0RG>$}Mhiae;H6 zD5TNksbs_-6?ho}gjpHK1PLOV)jcG`P4Hq_h(*pNq%Tzj7CIF>Uu~p_Qkn0AplA5B!ja}n-t(+KFL}mdkl(&X$!kmFjj#)qRZ#TQuE=uLi)&9(g=AI zZ)P&jNUD`^nzZT*-b~}ftin(5fBkF>U0eN)o3;u`vy?;d7IGTI+h*tQdZcO1m$xDqFVLT3BeJh;P8vf$6e2ehDFm*l~5#jHOIX zVzRcxZ-&6Q^o2O)B`lBn1%@^-H1(z$=p}uHilqJ=|*O)ii#gh zV=B?Q!tEW?qPuO8(50c4b0hmzJ5cuUgd_c+PpFGs!#{o2@;{y+tbx8;&ncR+)TLe^f z*S8oh`fLe`nJ8Bou%rNSiREzb)UwU8FrDmM02)#Z4eAECy75nvVe-lL!l+cCEv%r$ z>ZDPsdt~Fi^+o93jc?LuECj;Zi_7Q?FdC8bs^uoGV%phhMC8-d#)1!ue$(=tHA1JA zT}Se@)kAQnk!qz+dWcx~uc0ZEx5wd{v2mkvm#X~qi@~$=44X3~ZX9IG#(1E(pGSzQ zF^aE0aBklab-#8|%w^1|8C7zrAV>(+(n;~@D7IrXnY!19#g&Y;O<2(uG2*ln)@18u zb1b4)I7fgJ!znntSml?D=uyHzPhr{nxiHKX=gqFxY1%adn@eJh>Q`z%H3JZby_*DZ zM@|R4W6|WBTqxEjpef(3F1V3O=4Q0IBb$pMDL3KC2=nq7Oc=S)$aVz93UYLNf^CN7 zrgncwxBVnWW1=;>$id$Qx``6VuO)o8eg$>&o)L8?)iO1s%y`pe`w`wkL(Q)rcT2*M z!n%rIkL9~7m*BCJZJfBWg0VyI8PBFGcY^?T)$20UEQW8f0#!{w{V$|48e-87CmEm1 zeUemEl4ZnaXQ>v1F@q7W&k!=!meLd){R9`MLyN}K#J3AL_VPSUM2cYY{alwM;B49i zfpLONCb`C$-)Z8!F+B^Z%E^#Z;g~ABl*Ny$@~ROAID0*lZZU^2g=}DpWYN3Hj-_U3 z8G{9j%$GlqtWqWkk|`YqUSVB~fLUla0j*sgTaWqIi*>RD{URg>rB`psFnr#IG(~rK zk!NAjzo<8SVK?N?sk?ZII4}?k-ebQk9C1 zb!Jyrt(8+tH+c*5GajZRv;2k8Rfz2VEVWH=&7Bx)rUaGCt~*=u`Tpx^%BE5e%*$>@ zf*VbM>7*q7V>$Bhh2_}x7eO(~U3$da)zk&YGeR6uycpLpdV*~^9?IDP-bE|osP?o( zRs@0a_hdd~ts<`TmGa8dV=X7L2fEcT;F!pRapKHfTMxH}Yo&v#JFymm-14JR)2Zmf z$|MyBi=Ja4%ubYuk{Wf6v-Y+10jnbGd=fc+^#g*-oV4P&hWPKUpi~0iL4OCng$3n4 zKYGV8hp;ma5Ccp?@I1hpiX!5yKq6w{>V(1b)3;ugV9ERSp^Q^!(a2aoB1!6vN2?!| zjKJ%8n?^Rj#KB6z>B3FmuX5S;V)V|fGI$(7gqqt>qkXEMnzp*^S1nkM5?L0|H@VW7 zSAU9TTI&7C(ws!(o46Gs3f`w3hUrp+_ufd>vj{Y;pUXv!GcKVFO57nU%qp;(k5hW;LRD_4Is7UnLCBzY2+c?w%;589iZJ;0 z&33tU>Dm*F&nJ|dYB|3n$g^IAt)!%yyrks6Uh*LCcW(pVh{z3zk&KvXj$jy&uxJcZ zN_sX?FzqmCvxG&?>ews{?9H;<%%+sK^CQ>6A&BBtqqBYK6l_Nh+d_ybNlPn^PrvIc zou>L2z8lotb=&TbfS8D^Ia!A!w!lJ-n;iRf?fvr4xYrS{hB1q=xUQxAfPkqBPYiO? zvtr?i-)rxvEBBP)@1%AF%8BtV;4eOxm$`4yqc^6OV!p#pCiW2Dk*%9x>vLrjWI6ww z@leGSj|R7U?5KOK8Qe>@Nfb%6G$|2? zggoi$p^*%=M2vJAE#yDXF@zpzIxO5W2F4mgn6%`Q$R99*yzTMI>DEidP!EF|e{2`A%er}eUekxk#e)i@97F43Ik%fH(Aq*TXJwOybjt)-lf<7WtFMI_d zpPz@>sVH7ZJnTiN^pw>oB%R$XDY)6V**I9GeQdorsa_*f2)kKW32I2m{KEqBmk5=O zhlh(GJG-~HH=8#Xo3ooWJ5WGCfSrSros*LlLc!|p>*N9QVRdq+erEBPhm@textpzv zhpn>{#WPP3*xA!Vgo+9>Pw_AL99@)^|IOaX{U0kp?7{8>a$yIuaj-i&vj67_cMoYV z2$O#V`oFGl*MdAyvujwoJA1mBTS|LbI(bn4CkqSnf3J7(baQx7$HJW5(!tUZLh26L z75Lw_l$BRj`}c}x6Ik0iy1cA{82i6bdO%L+|EBzJvOUkdsPmtKK$idCssE+>3o(RB zSy@oZ+1&G4Jb5V*s^|R$Eu775Ed*aag7~<(d3eluSuF(kELgb(IKiwSFwlaPigu%-Sh&F)gPHrtAk01}HAU7`+`#;LEKgaaHv=wIm z|JXzLh2TGu0A$_YF-UTO@$^Xc{|7))QHP`>h z0{Mtrd(#XNUil6xSoUL;OzeQoi z^IrEp1DyZX>YhkC`1TWj@F1fR$2$}*80VB* z(q`~_29x+FVmE$-_VHAjAP?&&4Pz?&1+E2J4NwI3B zWXKo=A{3Gt>>V8kip`TIToCNTlo+0QqMoOmQuy<%kw{)Baj0ydtJ$w$XK2ENRWPy- zuB+zry20ELxYvLH+}+G+{9kVo9-$Ada6h8OmAwdCmjJhh9Dvup%Xw@Ka|p}SFR+ym z1OGgwCo~L%PU=HDfsf1j)i!QwC=}vq%Kz4s6`PVV>sd+{U$=f8Qx0y^{WAyH8!!u6 zMTm65Z;59sL}-5|+hPau5M1#D9E6a={K@84e%Ve3=neBkT)t8Qc@Z8MYNvZ7Ew3Aj zlLZq!n{NA$o*48Ui3@BRnrm$Ev%jqvVP)OVhtxwM=v+n_v!FeTd2WJkb}zRUTh)arC2td1MbUwN z_r$TzA@b^Fy20%T&Iut2KBgm?$q{;0IM`z21Mxzi z5Kl~FiM<UtosrboHfIq@Om$#GtkL~osK-A|#{mR{K{+2z98zdjK zFR*pgTICcAa3P9<66cUn!7$`MDIF`@V17S{8h5}wi`u~HgL>CylTYgv7XPN9H`RQ6OL)ZzzzB9jq zg;VaJT4vbMSThZTxJH8|IDa+NR)U9MSUk-O+wqIciY76`(!sQiiQzpER|I>$oewmw zSPw99qS>a%e6dI?)+mn4y}4OdU-(o1Rr@NTU1!F|8m4|*PR;6Yyyj*_4ZvNq{c$-j z)Ok+qUj|u_!*0O8!|yGlREBbjHSxv|{QARzaYJJE%a$J72iy_^R?TWVtL|p;L{StE zz!Pp-SjRGA8q-U#4BG8Mm50p5uoJ+1rMb0$w^ekc*xyqdmjG=WxcW7(+QfOo3p`pX z4!tU_*JCV(_(ohWgMnffB`Wz2FIsG|Gj&eY&JpoS#d0R|qyp!R&KiM< z#Au6PS~7f?BF_Ig`1F)ZLpI@02P~9fHO~55o7UDI5RaQy8HZOl@la*T7)Y3tK2kg4 z`UY+vQSjZ6W}A)4Z#m4TG5_6^74;6h-JGp6*GPfBVsSk~Om5|WBx$iuM=}w(*#{h? zj4~hL=eiYi_oI^APPrbL4o>n58xeViX$|}ekhRWvG~HYU&U34HRodDQJ4X*Y$p+;&5UfP)j%N(n@ zNoqrgrNe+v=4 zgp(}H{HfEH{9|%-?4v>!T2S}i8U1gYq}$n@E4aY*+g}olq$8#Ae;q1648+BHf}_(c zP7C2eM#muoxo^`!K;FQ5E5buqx3W{nBK+fx8*;bclW7=9K|E4S4NMkarFFo2122`h4nZjkn;g=WdP%?xU{e5_qM7rTf1>mLT(WlTpUj^s+&B@oC*cLN&+`7LX)%HE`3~+%k}V4P?|q;;mxZd1fWP_mn&G< z7<2rC2Am*r?9m3ELuKcUQ&1onfzL}=jB+DL`zS-Vg_i*(l4^f1hw+_x)4fiLeOvfZ zz%OJvI4+#w7btNHGMciG3|pYX_HO@3aJFi{qSR2??t~#{@911*`%#r>@wS{Ix|*Y` zO!UN%SVHo}sncV7p~~5H8OK0U7Jy}-gXoV>Xx#MvuC@G3{be(Qbo+ad7&vr+4=4G6 z;sD)=*zkG;S2{&F`8mM?+@bDJ&qMmU(BJ-<#F)(^EvB7`*BCf0Q(lLUkJJ4)cu+g? z4$jsw2D%>5+@A+pLq&?ff%uasLzcssIeE#v$4xtP;HtJ}0(B<+uUWPfBo zMj(ME>17w#_9gvM>2FzWrS&^0AxwyAqWsjsFm7Z`Xdx*{KcupvKlT&PY^s_cpJ(D> z(;7Y3L;gMuOu+d?NJr6B=SfUc{_GSR8jWm^asNae@mXz2o_wP1x|dE$z*LkQ{%`#D^~Q)P&XH zs0X%GudO1&JdUrlv3{oE?ex#-U#1_1Y_OfiY1Be_zHDXt;zO{6sugpjkH1YAbbk{I z32uc^PKnOkXCLcuK@SoJn9ZSf2E38k=lorx)6`Glg1VfH>K6FDr4!|r)4RVVM`jS-FI3fRmT76}SPHHpvG)XV^1^A> z378tMt%tLlZvmdX)|>K%p1khWf37r`xENp!=3qE%Sp7D5B?Te96-#3;|Mg~CHW5VW z@lTvR+#i*EQw5x< z>&`1ZA{;zCTw^*bT3_?+qb+pDS$0!Ec}UxLV9?*v2TFsx4-pFKnV+G(aANX{YWyB= zq$fNS;K|e!uRBh)%d3vuTbLgIsg$553kqBDIpJXP{sywenvTGpN5<)@?|~<|>HVnk zpcKFQtnB8X1Y=(nr4f{jFa7OvnDe6{#K|zW-%nE82~b*nSFnyhjT*TApkaETG&4je zO4L;3)E1D{pMv!~+*i;Rg{BL!D+J3Hm_iUj37e@XxfvgO=^Q7)0rmM9SUzs9#8Mz- z&z|~8O>ejc_Qz3Tsdixw$;43>b@n}4DAfx>D*5CKiSH24Da|D30fM?gX=%-Uh2_E(nbK306vu0td>1(UhTNI{T!p_abJ z=GKU}pB@? zGii1gzf;^yJCvDrhL)<8l;pP5#0;1P>g=Q}L!@?^wwIQyi0tYYUX(0i75Y$Xd=z1j z@K5dWR$pP`cn1=Jw#Wp-tf&GZ4s9xc z$oxsdGwO1d>RYulyzik-Li`S++24vXEmsO;%)l^b68XT>X$Cc>S36u2!FaRBkdE#> zrofol4X2jN_pM+Ah1cnVyWC{R{(K;i<8MzCMuFh~dkXCYd#^j*zo~b}_zhoG{Q|ED zPx;<*_LF+tZDBg`RZ&-HzIg5J-#DRYN4q-%0#R$3ZMU#9u#NNtCw^TrcE`ktl&uL* z#*&dRyUa`@p}tBPk$q8T5J6v(v;Pe64(bL3CeR^~*QRhPvr)bVA@r^GU80-h?6>RG zs@Jh_RsigC@-NS3Fn~u{n-r?uh&sk0}YXgxvFp@N)`QnIq|#;tcU0x*l5+~Zg|Xj}f&_n)>s2bN z&neC*y|OoI}fNQY8P1S9CAZre7#b}4Qbv*lKT!^WJP@?)lbZP zjMM3IiEqI$Utlc%w(i$7a5Vt*UtNo26pbrvm$e=5n$_}(%9-E1H3hx#${QP92@|e? z+k5a|yb;HEZZZ*di;3?TKGM##5y1ia^?pbnFyUuco5a*udeQ07;8!ayekW71Jv#pQ z<}4I~PZz?be)TPRr3Z;jT($j+a2KmbOR!h!WTXa7mO-3%qu#srxZ3_$3^%C1@?;g0 z@5bG6LkhelYYlN)d_@jR^3IU~%_97g0;F}i8uls-TVM%%r1$NylY0ps7hoKL=8fyq zoW$7Tk%%$ zcps#?9|=dZw5hBw0Z#^+b|Fs^QkI9|hSB#cLiYp;d0` z+Ud-&vfQ1hR66q^y%_zJc)uw3{kF>QwA@6vZepeOBl2C+8Y*N<6B;vI;-rEIs3vFk z?9W*Yg7F1C#3w&1fIeS^BAo=TISE$OPaG5l@8i4dq1YR?d(6 zsIYqAN7fo?s3awqUB{}t98nkl0 z1zb<|Um=#`kKA$?vws_YDU(KMr2U|+ASKBXC7irZqX;;+4iN)Z%53mS*QcayRNMWT zsRy6m?hJ!{7*h;}?skZ2-lG1x$1&`LiIeC~Y+1!=$r!mjyPe2xnq9hzhk$f&WpJQW z?Gykx&VmBgi;$yEvv*=A(T#am(z9VvLFQw!D1e)^rcKNNMpUpLNTrs+J1X zK8k>>!$n(S^*YeB6!3=zfn++X?^1c`I|F`5 znYlvTVjLJJ+D-@`m+OGa;`xiT&EDQ5S1Unv$Ogi|vPVufAOu{TmYsph6E9KJz#5IB zXkE#som10;TWTW*BRV{9-~PQD=dX8ySRfGE?UxM^g7mkYt-mty?uK=IY*0KbDUX;) z6iq=xwJ~4g>QGP4Q2DMKi>)EqhpWqjthU5OUC-hzm17l}IJsbS73NpBT8vak=r;H@ zLT)_*`vLvy*#@XZb-l=ks{NrV%S+H*E0oxC%irzrz!;;m9#-6Z>4We3JI3MYCMu|p z{c=3qaCS+>$$7sBS(eq`>!&;)y(1Qsk(MzY$jXnjgyY0f_szV8ggXERnsKGo_4=(> z`?&iCcjm$77ZqCF(!99nQW_e9AZxj<5kC??k@BDWx6;q&`x-}*Li{<&9hqR)7;N$S zZ-%SeW^JzWEoGEW$HzocA0x{n4j@`@v?Duy6Ezi@`c@=m3LQhI_j<8dy|Z+OMRvd@ExC4$8Zgf zwf)2Cz=!U?mEX$WlRQ(Qh8g2~`Krp9B zMY>@2Xv3b+GN6XCx4UJ)ggF3}(jc<9Pl?+^@}6U;V}!5DFGKD?i!slkhHx|v&QFgb z^2qO3`E%lnSWv~9%W#7fsK&{su4!A(Etlj^smt@QBF?&^=xmSujfI%ei!Jio!ml#h z#Dum^fdZ%n7&q^65fb*)vFh|nDO^fqBi^6Y3e?Sl5%$$Y{ybA zZz6U0aGb{6vKP=%xXCgru@1&!PADX6n^<_+rq&2ae?)6Di>M`Wb3&=ZZMTtw%6sV< zYn=Q-sr#)%p$i$A23$0`gj=K=DVi;q4@tSl5U zWR=72fwxs394y0rjl;=BtbS80QX8lH#;w@`z(oTlL^A!b4}1S&u8ic%&94GV%*U%7 zjL=DIm%w&%-48c_rH_v8h?`jK=ZXf}&K*Wp2A z+RzI=a(E{=^gFyLIT$i$+fZT!7sWPkVWq8d4$fGmT-S7lHUY6Le^b%UWWye-S{>__ zfxH9mDJ;ziH&93$UnHi{it?p+Stm0O_Z;rk$l#^J$aWt{Bq=&MK)B}(z~u2 zaDm)S@@L1VFjR0;iSdw zsc?zyN&;mkdh6;b1X~M65ZLlBIHBCc_~@oD)Stdtys#S}8%g4w3{^S&lBfScM}fx5-oM_g31 z?~-e3vkxxZd)W4Rmw0lAs5)cC1FdNmU|N!W4WvctIamj%FD|hPf3Hr9i*V;seGuq^ zK-jN7G{gG@hymA%zphpteUTWP(R<%WKckbINn=69z}N!;#2l^>7Nu#kU^`Y_sxbKCv?uNV8>Tprbg7c<0xlmtVHS z_NHN|*9w10p|HC@>l}w}Qui6265-SE0R(qv9Cb+a3Ih#1)dWsaQ%<_5ZFX_789X^j zWrttv+<>I_9Tm*jd*E6bm?*i=3MpR5{NqRl^gQM)CNJ$?#GzU?MYB+7Pd{s~S&Xzm zX+^)xJHa{Rdm={uQtlis64LboTguo-*c!zBkp^t$}Y=y?{X zz?b`vm?Q$ar$BZ(NiYTn=6*<~p#ix*0<~jqD4q1{jb-TMtdBz)YB7@5@QCW%pS7km zuQMh3z>$%)4xBo%aL!S21xVkg<&{PiUn2R{3~B<@)7s`pSuxqP7iHI6>apy@YH! z%WJ3?y}iRQ%_aRTN$*!NC3&<2)tcGZ#rI>aZcZNK5uI(e3Ou~pPuEJBvN`X2aOXkI z+3>akW=NHB&sPeS>=R{$XM;HQD`BywF$p^OiKd$TuZz}%={aYX*g8qzBpSL9NX6t)c~Pf zP&=;E>1@peJA2*6``o(#61R))&}Mv<{dBORpd1v%U^g12n;W`jg{o& zKSdEZ-WqKevsSrn=Uhy;X zLoun+%D%0c$_s)TXn^YsqJXt1#`*Mk0}j-0TNjQlmH3^UDT%cv&!rPr9)B=t3X+=` z{eM3CN_waky0Em7)pjg&v7o)&wIITyX!?WNo;uLd6bOAAc1IT(y*}7ForLvvQ{e+f zXKSKDARcYzK{9NtKoreIJyXc)mS$=_x94Gi9Juw9p_JhEUIGF(Ix$vz8T|pT99~lJ zyEvm60|J6SH-f)eI~+^d(lDAUi(E(UfYnGcy?8VYab~wPp1nJWmY-4sVH>YRex}o>`cbO9@uUYG0HL(9ktEoegnB)`NqTd zZPhYCu1mZ*V_C!TZb1WZpd0B0eoLw_ns1MGv%jSCnc1;Rk)7P#g@9%A4%9GeCM3u8 zy<}@x9Tb19vki6KH1SPnI8ugHYq#qZlR0*~rkbE_QmS=|#2U4e zCC{%f2qyMzCCRpN;kcI8_$yHX>NLs&dIQR#EzW?8DhA1qaE$Am57Vyx*X0@}D4Ai| zXs$wQKXn*2t5^$9v_#h6vPQIB!CpQ;lwNS#{f^R%xjgckRg4&I+UC6|H2DTBpZ8D{ z45S_34>K5AjJ##B$)qEd9;`;te&7CT4PhxMt)*F zmbK?makdZG+N)4eud)aTYi+EHOz9-Y+UO8mVU zoBL7^Z#4%kqyh++58|Vvw#=A?8{lrd`sJ9Hb;f9+br69Q+&fKL?RB}ibsBY(Z#o=7-}0Q=+(8q5O>51|9kP#R%vv$zHSN4TkXFa_kOH zVWf@?jfG<)Jav(XQOq&$qZ%}%X{iK@RHy1C-?gU&17^$&d36G7h=ET+&v$bXJjsKw ze5Wu$7`vHr+~Zp;IMMz*T`Oj~U7z69c9Po#6~oJH6`2?5J#4fo?K{W8j(C4eZiD%Y z#Rd48bdn|U|D27XJbPDff*>WL zxIw+2OQ$s0TBK%SBp1=%xll=TD;2S1gn-pshzE(dK_UZ5rCwhy$TKCh()2Do5ykY3 zL?bN5U{lymQgDPQ?R!h!KhN(!m(Go}mvN8Co--8dV9@F!=- z=+!0L59x&hwx5r%jtNj}8L^iz@hlH=s*U>Rw&O(%zb@+-XI^5J%jZEr$4gDnZ1`#n z{NP2Xw55ui7KOh{`qSvqcr%OOEV;=PHZvsd$^GloXF&7gW>zRlzpts;XFwXoD<|k( za9A6Bs89$oa3JZIOd}hK6Hm^oX#b+VLK|iAwaifpwKC_b^JE4H84k+A90Lhc2MoNHs?1{?Jwz!uwiy?CWEPy!-`ww3>z4JjxM{hQX!99$Q4~J|^~tVl(I`a? z#|N68{d27`DD}~KpmfRcvKC>YE(V5RK~+CrTj@p`2@#~bLrPK_rMnxXJEglzy1To(L>i>KQ@ZnAzQ1|@aE2L~ z%YJtEJm-mX@68Ib;V0NlLm%%Gb#36~%WN!Cx6?1kX2}t6`eVgPrW9~bn;jKtj!q0K zcu;t3{6t^V$$h3lg6F4L*F)PX`R?+;M|b?iF6ZS1FML~})8}RX{G~lC*kP|r_^D$} zorK&QKCDDv$&$OA-&oyasN3enxP{5GT#vGV>u=87eEEB`fM=imc3el6ygISreugGG z_ct3FxrD7jFuG(p?j>EGQYg;?I;(vjF0rmfe%$a6FnY)1{Ct6OztF@Es*^#vB|T*v zUo^RK_YkaUA>`xDRZZmhQ3|bF2WXUT9@A|(;SO3RwEj-yL&kLewffJ<`JQvEeeshOd z(k4-%em9$N%@6HG{{d19Qs!;PoA$`T-@ml|?cVkD<{d#FLkBRur}r0qujAEipX+Y$ zttwA^D%x9+z(UfDp%l8fxjUO@kwZ^W493am^*JV7^RLBW2`qC+-SZf`Y*FQcJV zFeZTyP*71Jo}mSOnw9x|7}x!*$Dtt6Nv<%0Q9;^(f%XzIk8iNstt6Ol(e>BXCXhE@ z{~jhu{S{KtUSnsIqf@Npj+CG>?lN4(E&&GneHl+^L?#Jv3~fqJTdTpDi!N!_>n> zor(zI_p@3JU2533Cmc#zTsdvK{p^7BL~?h7){d&W7V4%EcqAR za_@oT+s#!Us;5@OSooUuiBiOjMF`)K$|^WWk>3OGZ3bu4x>X7nD(9Grs65Duvhr7% z`6JBoMVeY0l&lGIa|zcp!qvby(3J}4toZ)2Az6Zj zD8F{#sO|27d&{fM*&4hbJz-LBhl7z*je@au_mNyb*PC2%B5^z}#yS_LzM(q+qSs>j zO#(Dx85>@B!H6>ta{BeJvd%l`5kV~Ml90h_y?mq##fudEi)8;ld2Fha{<$;qic@HPh3Br=&c=_P131Y4O51A(3xo9ksJu|+OIXQ&;Z@7HyxB>i)@s*^5a8ev-$5{R-4uA80$`@5DSf|WI3b+9<{0byP4+H z?6|+V8q#t~xPYI%dnYcH{`5f#Z<$?6Cj%~EO(nwVw~ho|gWeMV3kSKh9cy>hl^z7A z)mzIRgMr%dFHa@yUI+tpj%=oGb;nDS;P=j5WDQ zDt_`T$u)K%90hC?*bGj&<~$c03@_zXl%=vJqQ;nIrqcBJCNk6r@Y2L6BUC1`DKo20 zjp9O@;f>UiRu*u1-8b@zSLj%TD<8leddEIQNYU^%kWrB!a{TA_vyw1!scV5j97HPZ zZ>cCYEf#ktsWMWTn{c3a){CMezTvoILR6Ow(R@`+gr>xTciH$?Cm|NqbP83gstQfT1>6=Q11quM)hm*_l_PA$ zIo~Tl1#704`!7&RzAlL=3~>YXNhC)xpY-pE8~15T=Tzql;$uatIjZUja*~zwEt{01 zly;8H-;w2zr!?za<=Akl)#`VIyVzlj&i7#(i&mzde4~b?aN%7}10%+&!brQXtva86 zZYF!5;R_Yj-4x9k83~-OP3Ma{!juX%l|1@;-(LtRpE@)2Cs_s#o!cK6ErCVJ87h?{ z{o5L)o~4-7Dg>i7u2PiDaICqURgWz zg~^aIvbn{#%UpYZpg6|tB(bq%<+K4Mqrt+NQaJeZt6wgKu?n$zFeqX>9UInNQi$Oc zODB1432Ovbxi=ipwUA(a6V9>g-TGh6h$$hJO@jvFy`RK#XlHed;I-2HX7ks|-a9|1 zZV;_ZB3r38qT}fksXAsj@f2c^Ia{bZdH-9O*7QHR=?lYinfCNXvRz}s6;R*fHgaYLw& zTkEHnpRmzKo3xz6BBV1@lP~X9P`%h;eY~>^ADh3~bBP#@N_>h+(|7w{iBs0D(@`8T+ zv=;a)8D&-o9fRX+3@jq;*)4y)RN~?DT0VWFS4}pJzGJ--lxduzEK3vI%N?@wLD+^l zgQ=FfhPvBUhD^^`{(6g*>9zeYz7GxdJs-xi1sw?Cnwuxa*O9Bfpmb*g*- zCy{leBj3s2WuHfI4ei_b3GXs3(1w`pZ3pV3+ZsQ_YRexi1>HaHMBSHgTw$j42?br% zBnfm#FGrbrg(6ew>@5pUjbwHl<^13@|*g2xm+xG}!SwKsR2Q4BDOe$Bdbh?xE z{(^z7wZsl7l9$FgyZi3?3-6G2Sv#sV zZAwu;GejXy@@;uCi?@gzz)UmV8s_kt>Z8m&%R(UG!x+r-#=+K^(dupamJNO~_ zISnDmQEz@d6X6V1j&}~n=w;!pwDyZf+oX^b!KqGRnKjchA=}}WA96?8bX@Qb)@*zmtCVEfi+JnX#<-NNk~n2=C=I_^Nd3+g zPe$V>QQ8C#OaAQ&RUdUj#J#~`LUJNSf$-6UqLfrc#j&VX^}$PJ8I{4G}HitnzbwRo81h8w#moHjFQqsIC9 zHq6S7KtAA}UP^R0L6qQV)F+z`8b2i2gRrs){`2HSeR;Vnqq?{80G8K7@`iQ?Z5mNU z;&#mRDlQunO1hE>>km_m-O5%ISy~fv?C$uvdfVXcxt)Eyy8F0jdVL>aaPPeBAWth8 z6=C#C_Pc3^NK!^(MvNG!2=WUGl2TG~H0Ud49$NZv{F9xMb=XJ)T|w66%bVX7r!Qjl z%-@jt3bh%}ots7CRkTojFPE509{q87&wtrP?9S7)KobqY2qnJ1Qw;NqJ(G9r$;&VK zdoWdL_VyQGZcrn>o&VacRcz>k^Yh7@o11a*@Urvr{N4Fvixl)W`@`cT$e5Fc6;<(g z#&YC}7}?qTZ*Ms_H#fr~B5Zet-)p&SW67j_?!&igt;!Ck52wby$6%a;=wL3zwPZ@8 z)UWsdVVZ5J`Si%uC)>Za+P-#{BOa^`hmQPsS}R`R?jdn8A)U{tK%6DSaBM&YI~(#f z#ui+WVy$I4`qzy?>$$Cq)cuUj?!KPy%p;?$jPvi`zst+ZZ`ARW@e;MQwd0G6lI$sD z@eRS*H_b{Oess z9VXq0N-i}LUR0gUjTlPom515SizcEVBt2U=UCRN`}Csqs+_+IFmZ4U3^8;J zPs#F+y#8YuAPpcEMuof zHDl*DUN1L1SAXTL)QS8g?N(vt=203drtFO{;lJt^^}j=UVv4N1Bw?0SpI9A_0PB-(UR4muwry~+a^kN5__f?1m~~&d^D;zPooZ4%YK{)DQx1np zj>k)bUB0lY4VHvpe+09>&?oj(XXBU49sIKhGlYSK-T(K`@a6eQN?N+@hxhFcN5^23 z-EObL8U11Rm$#mFiF$Ui*`GF}!v&URJV*S&vUagC$0E$1?+riU_V&M{PdX4kvWKCwJGdk2TtE|?15c6AVQOqT$&4mp9q zv!RYlq8+a}OO`TnK0oG}&~n@yN~c~QB~t*2Xl4NwLCD>|-V=DYy!xcs{(Mz&5nti% zkE87#eRg)H_)^K+xKdm0sl$R9d;gDIBwX#|#v_UQmB{Tw1M4b&qEWf|=wv2l#I|UR zE!VEJO}jhwH$sTireE>kX|3yWnckzL>Newv9wf|F>Ou+$2`xD;{y19g$Uw$t*I?Dx z&5M;H7blN3V16I*fmb5y+gj6Z`X=}49SZ;5psb3TnwsM5V#ZHEL|chwwzGJV>8PZK z+wT*8f46qU)@sVUTM>7*L8n4)>)*2vr0j1ADh?AbtM65y8xQxprb7b3VE5G|WY`)4Qj8*p7qj$`%MH#>E@Q@hb*bIU;?Oj|lg7_a$pTU6hD z+F8ND--Q>u*5nt?#7Fmce9%02dH%#7BBz#@M~I%+iEOuiqa~-Ld>Zi!@{ubNmY2tH zyFSugtpDMDRieH9u=>K6laoVp-&`NR7VBG?GY$2bI?sc}O8>^>v{tVkLN>#{qV*#~ zaGk`k#D?10I5IL`WZnWq-CBtNc-KdxC9M=e&)ZD8Vu4caj`WA)mPoggw#4XYwCeez zWDKMs%Dl=SDcr}6zTo&zFQ;TL@)$gVYmCRrDMNeGNsjiv5gbn+nfVGr$c?tBD{*+r z7ZexVtyA*wza5kPGo)7*1LE5zI?6IZEEF~KKPx4acXEmK{z~cg_5#Y)L@bKrNF-Uk$gyz;_}^b!4^G?5tnv_cG?w`5r3&BjeWyf_3mR?iVSmTYrn>!9`VS zhx>k1o?1!-_x>2NtZo3r_Y!oc-7;)Cuah*hUx>)%H*oV)44sRY9h$(UaM_CfI@jjL zF=!xn+^_g9a^O849!Z)E3+Sk;Ps^(!$Hk3#(AV~znol7PF24uZ-Yc^KZhB~Fi?)*d z{jol?KbSrC{?+6N=;<&4t?N<^q;GD*o7e|zoLsJxf& zGVcEt^mbdPk)p$YaM?nm9ol$BU9U47FSL0T;Hq3>n@GBq=8sPoj8b|sN)&GFu66nH zoOVJkx$LHaZWE(ZX_AjVXTS`2?@z_zn8OQGeC&*#IZY?wh4+A+zyEu;ka&|8Np$M)ikv;Y}G;; zdF1xWDX3F~L~$$hiI223Xc&*Ue^@3K7XA=AEp&O z%J`*YYbysO)SH_IQBLSN?l@BN?Hc>w40lv-l8(W}AlUR>C#LlQtYg5H)ROCA*^>Q~ zw8cWLIFt~ay}f;<{s0OJ5uX%&BI3VRyv_XEK0hP(6)22y;ru_z6RIGbRn%11#t9Xv zw^|)NUT!fpYpzYSR%L~o*jNxxg}Yv1(=7Tr1NF-}Kpfy=Jmp(;@3t#X@&t8_mmRBT z@`hzc{64+!M*L6!vIz+Z-JdTq3TkRDVz}pA&t@5@R(>hbDouyD{|g)ILB#m0$Vi#D zx4*v*P&K!$EzS9HGB0>6op+!a@l>2% z{%JnB!0Nk$c7E3I7_$Q%6or8E3rTe2hR1}OB#)4vfkNnfEnx8tV3Hn$rIjAEE9AY?rruB)6*R)$3PVMi=PF1n$Hm6ZdE1oWxDL`*Ze zfBX;$D9wl9vnHsvwl)Gj_h0Gh=>b$s8P+VUeJz#oU?^snI+j+U;Lv6dqjN6au(mRv zBkh-E!kKGmz$-aQPyb*e$qE;-88x(hJ2${5Dl02%vdhbsb34yDte9CZsE6Obi-q&& z3g4V0T4%N0!+=@2VwoigY2V&enx|ymBB8yVkMD6E6?CMTclx$vP~sH$!ESmJDTWg% zWdm6hj1G8!q~K_CxFhzwaX4aGZ>GOn8_`j9gA4I6s;6C8_qQq}@yRZrVuXp#$F9L@wFW>~Oeu ze5{2rW(vH8!uaTmk(=DB%QWph>WkmH%f2lWjx*HaM2z-8j3Pvv1 zV@?RQS^p|hTg_Md2NHRzETJQZ$c3bFJK3&vdY{hduGJyt(_Totda+e9Nf>!@!C+S{ z7TfbR&5IP&So|jt{2@Sh;*974Y@z9}yydq6b99~s2ifPr{^THPefNCkE~#yIUX&-8 z{L0wZKbR2E?wXznSUz|mcB>TvS7^cYgIBQ$YJ-)7BhWEJDBC$w za6UOID9prlF^xFd~q_4M>0p`c8H4^s3`#nWI#LPlod z;UP#C*|XA~ZRMGttN18uZbXCqw`o?`$?1m8iyu80s;sQcVzHjCx{n13Z2t1{^5yll zo+>t22e_`_wzU6VN>gnR{zE6sPO&>wz}a*e>q>BsNxlt?N9*q}g?3oK7p*$dKyUml zRq+OxLID2S->F$IdvU>TB3fGta9Ctf$O`4{R4EJ;&jt%`BKdOT;n7_|)zUpKCYn@0?m^T)^3r?B#AHseaXVFhM>ny&t_?lb#Nf;aOa z!OGAC;^4d<^e(xg-qAE})eg_L;ah}{@e(#Y$Q+=+1XcZ58RchE({i#zdSYOwKVxT0 z6im%(Qse+s2i2&rIDq&M5Ii6#T6a%0C>c7O{(lx=HV&p7__Fj7KUdDabw zZioasl!=)c-E0{K8dG1h_Xw&U!KEdKyuKHIB(Yt^5Fdtm90+$=nyhAyJB0 zamRlPBGmCQ^IF7n_0sGq+t-U$#=w(X4+^N>*$Pwp?m>wMKVb9Qs0P4w>1UhcW_r*J zEx_K#^bLSUaLOm}efExA(yki+tBC%5(LOZ>qE*$m6!`mb!!XS7#*(S{YLYpv*WP$|cx0s>f^xbq)w0oKz?VMa1yaOfzS>YxQ`ydr1u(`AS5+of zW@bSvD=U)(rbN;ogUG!{ChS7#<;?B3FrhE}ZdM=2W>r@`Jk!-1uFFbVN?cc4>gw1f z6AW6d=9nUc7^5(ZVPdRhViHf8lXS&)(h{Xc;ZKr=qf&`UNon84=%90RzWd0|u45pM zT}_K9KGResU#vo$!NXYGkyoy-;N>(glpIc=mt zNg6MK7CB(Gu%iaoHB+X(zrRn69$Xs2wddGUf#>|hEVy>&Y+`N>BwYdNvTE_Sv-y(m zLs7}UhiH@BL+&nd2RC8^kQhxw@Ms^IC%+*!hPkenl#~Fd?ccXob86}SrmZde*F1u|(Qc;PW)yEND7tuId}yBwaI9OdT7MI|LUH8oSt%>U-ZLJM#T zMnXv>I)3Dz!zb8n4*<27?z|BUay)dw)7#C+i#1== z$;CxC&5H_v&R2)C_ZEJ{=Pzo2?dqR`7Q07!!|#_0GhR*V@|xL~1Z&L6HGzGy)Mo5Q1{?H4$U>mLbFE;=Dm z$ET-76crUkMw;T)3ST`E@CUUy&fsBu|IW_pH!LEjRnWV=>Bw;>q%P7(HF2E@5*owG zMxK4qQjy$B_Q5Y?Id2N=S#Pk2uGWbT;OKFeh?*XDBEo#@DDrI++k&xBz{Q^x^Aoay zqGI1>B>yNdbv!}4(fDpRdU!Pnd>{;29sya|F^3l~?=L^C!AE%Svec&c6eywDvS-Ip zO-4;_3>%7uabCi?w&a0J*Gk2+r!*J9djpt%{f|W>5j<+#XfeOzM;Hp$?5SIAvCnC?QB0a*ipQg3T;Iu=mIfr{t`Col#5BZ{a ztZ=lP;Te(xSNChP$Lr)6bzMo)V2wBvSMYx4Ls29q6eMd%7#bzwtpy*9HoNYq<;?c> zbR9;Mc^&0@Qk&0U$NU6|!N~%*G$4Wl-PeA!^(JS6A{dtx`ePobI-BbwRZwt&t1qKM zc;HzV_Es$hfQdl!!@$5u zi4YK=z=Ab`26?)2)1LTav3}V;v_6{>3gT_gB*t{PQLC9th-6+I?Jl<1Zer+|s1Ek8 z_0Oqvbd!|LaQ%%$k1w7sJK?iGCo-OiDLDgIxGH=_MgA#!M|<%=)} zB8ezIMB+HeTbh`xjXfWY?HwF!eC5AKd%3T8F-v|}01WwG!|JH^W1sdnd3pT`-pZDcDF>=~3J+&!*|iHYwvii_AQdf9WYLCULgJwLC` z(oaju8|T%PNefVm%=}|WP$FJ1MZoPCraus|e&rfUzDeiTX;gEzmlIj-`Q-YUo7?4n zzu-L)5k<6E+#?@28lDfRM2~B*O+eB&JzXyrm6fsmSoYVvEvl9G3P=) znHc^Nxx(5v4FlU$#jP^S=^HM(Y30FtCFNXVT%-g8fsE1p&&XWJ3RCe$=C_0p8Jgq! zF_#5MxB^okMFB_h`T+JPk;%z;7VP-{#V1{7n*&X@+X#Rvk+p<^as{<%2QrQS7W-q7 z9RR7+6G|f%|46$uC#JC~RN6W7SMm%4Dt-|`8GHde`m9+tmE5jw29K2wIbyzx|LBuM zD&o+o$ddClpDrX8ljG2dd&izi-S<^A31?s853XD#$t6=q5#?Nh$NZi&-UgCx`IJXx zdUZFN{GP8d-Up`yq$ce>Pg!yCMg;e9Cg2de8D8m$TCTx;eImdr0TkYyPuu=#EM6>d zYX8-a;dQ${9q@V_U}a~=_DCDA-T3O$N+O)&=Amj)`Sx5bz}d13j>aOGo@hBRKE3jk zy%O>*SnKKugK92uk`yxwOAko6zz&hdif^28o;jzw^FEIYSp0g3Q! z(ysJU%s2XnMLblXbku63qccj}|UEXjHRx;bPlFFmHiPMHZ61=GgLG5i4cHQPHLxAdEGq6!_CX zy8+W-d||>))APBRmsTm23LhUIoNBWKIshQxBoJdF ztlMI(eqx9n1eR!0dOAI|DI8_^?LS?g2srjUo<##CNv1S{k2Im_80rwC*)3`-rv~+6 zzWy8jOh?z*?jqokfGG{EBG7C-@24^*S69bC#I`$@VQgUFr$Xx}PiF>jTZ~jtQW6m? z3pgvmp!ICtJOm4qIJd3Do)>Q-94?!1V|SfT7oH?wy;PQ?yMNBQ1|P=g4PN`W-l8 z^a&H;l^lS^52(kM=7}p#v>5m>XKOm3EJ8y=X&D%B*hJ9e@Zmpqb6QwpO30?NCC!#q z#^xB*W#g3fgP~UGD>?PtE}tR*>>na8HbNQxGvZIz1V%IXWk09U|2YHn3GCX=a1u-F zX*VL2uK?KBxf)|sqWkf{xn>6%2L}hG+^ou?IO@r$5uwrE{xDtCt=vqdcqx2JbtYEU zUNCO@gYPU@cFnfw)U_=@J7w9D0u$kETnP2`pQZ5~PmVLwyjTR&Q_3M|;$x0j0gmx!wH|H%K4=v#5&5OIz zKzW48On7-jo_WmE1v0Nj!gep{2zp!&CGyzyv6siOyDbX-L15c`PdLO9T)TMyTr;bt z219zvb6wJt3_Y7tVm>5{f`S4-o5VyYr}=W3@DkxS^2y4$BzD0RU1-Ekb8OQ}Yu?{E zI9MJG@+p}OoTZOJGA(`jjGt%v9uDov-z z9Zy!mr}k-83s;cFS;GM^^-O=P@cc)O9vEItH)+)p4B-e}@3;QD@h9|VGbIqTfGlpF zuJ?d0S8p*dmtd}h*>@?@qpz>8(P*7jIABq2)ni=IZ;qNkH~DU8v??x%d1D-74>~Gz z$-pK;1aAi0_6vkBb(7KpQN_(kzaA4g1;u=e6NC1{!YrsZ(j=*bE114b8<}*TL6CR@ zBt>;vcEFjQp02H{TR(HA?tW9Kf61pQb5)}z(Wnwn{Tq}#o|s$o@VsHx8Y&@s z)&K;4j;Aw|=zuzZ+IK7=ti79OhcaZqyGVCG7jM5Cv9i6~n<&vlHR^s%8ypTWS07ex zZikKedO#duVrf}#G=b;e1A||j#Y@@5W?&HlvDSy9ZJ?{`?bPwG3_hz_6}s7~b8CzA z)!PM^UPO~y%_GHTacGOpTNHF6J~j#MKT)m$o1H~Ywr_^;76m6NTU#INf5;9R#0(`e zTm;Ly`&5AZDD|^Lh!na0=k7{$WMHEK*>_stbzk6qAfK^ORFKYG+uuzaC0E_rs!=T_ zMD_&@fnL$pylu^!eKT=XnB;Zl;Vy^&{u2n!XM0;)(EbaCmYVEDw83{x@;XnS|6Vs| z9uqW*G2k;&Fg~GLTjJ*WiDjy?T$#0~%K?F=LosfIsT$U&*I}=#uBN63u!`vAc7TA> zI@1F0mZRAP7$4yJeWi{Ulo$*^#s_W>`8&AMh2R`hHE{y^3K<2FY=nz@S z4%hrjCyfusc^F3*1K;B^tOh!For7 znyP}&m-MBL-UnwG^6_h3D&zfv$Zvw*Hvt4x%lYTWarj5_N@x>UvvIOqWRDdq#i^)4 zgICd=aW`!I+GB@?hVrGpYMl67`7^kCg9u^e>DcKt{V!b*6Jo`q6L;NM3HqW)ZOrGa zXmTq4Y)|37Pa1H*Ub@yBjN$&zTw5QUi>$|iym@oGE(i@@v&UY&zIbI6t^-q4i?4LC zv%)e)V%v#L1pM6^?HzN0vJeaq7bllruuTiy72ZmB8tLGVhoy(?!4$vM_GZM50GvzP zIai@n|Xts+BKC669jUNap}H+S%DPE?d3^_znlM8B-J zdvC8XJ(Cg90%P=DUGIx^<3K9|Y=n<*6eLZr`-_N(Sh7P5+_V^(f>-U@(2%&ZW3*&> zQ`}e&=AD?63>+QnN%w<;zV;~TF8lFu+beX@IlLRw2{OFKXX57vzp*uMoY7{MK^QDN zlXxdzq2S;;?XsH3_@1+V#Xrle4&3_KPe#;u9)qER~x7{%A! zt<9VzTY`NwBmPq>eaXO(7+^&fHQ`Qm4}u zp7EgjU!3-!*0@~kC|eNkpi#9CZ8a0DtuSJQyhw1%!{`{_L<#(@!JZu&sal2tueT)_|(H z#1IKEZYYs=H#K6ucN4x^IhBOYxP>&r?|J-JsmWs|)HO5=TwU=p#s;*Xx3#OiUOf8; z29ybP8?O=fLbV@B1mU{Z8KcE=H*iit$q41TtRnmq2yTIpzbqzkuGC}*IZ|iTYFJ<3 zaypu03J3^rzS~Z`TiWGwQgpYd({pi28uPraX}w-B2T~mS>Gmq!(oB=ikPK;J7^$bI zM$~%OX3ZfaH`OQ_<|6Au8+q1Sl|ZfEDuFB1O?6+er5=`mrfsrbhj^__Ue~y;uC5rx zwM0a4abMm2=H}1;xd{}=VDJkG2`Mg&RiO#WG4C8fABz}{lk`yV^f+QN8fL9nTY@Bd z4M6G!@Jo%J?ypKFciK8SGG?{h!V|SUlR&f1u7A7epHbHCw5ce>X|erDbK;2h?1%@R z>&I_&#UN&tT;2FiZ>_?;HyZvy-TmDsHF09f(x$jBMs zvyrbawCZt#h6Gd59IN!%WtuhInP;?0f57>msDRVs-hRSCXsKJarUIwofJQxqtW(f6 zwXc@gj~EpxheiO~pSAQE0qo9=9i45B9$xfOyfQ=82 zjRE$#zqeOt;xwG`xJ7Za+>%&TRTc9t@I}8MbD?alR?!IGvJM|YMn$UUFFNg(1h*&8 zhg|?k;FtMzK`$&Xr*GA=5;^1o>=$U1jHe@Gm)A8~ul*zz)ECH$UOeOr`dUVm~j%8XXHRpY`_b*zr6UDx+pZeoZ`d))#Jb(N?Fy{x*$6V%I zNn)_O8$y=vj`TGk%=!jbFr{iU3rHFmYTN*WI%Fk6MYi5UN=W%?Oino#Q33RB7jL~uVhb!e8f8Je{O;WQ13Zjf`kiTBAkG%+Iw^qcp}=My-20 z5LW=>9~hItKo&7DY+7@^c2-=Z~HJXAntpUmSe_UDZ^~;M$#d;TtPZyPTYeXnqczPo4id=`SN-hc+Ft1gBUN5))sE z?uW<%67g3-3F0vNL6skTbLcPy(%A~P_*vB7w&NZpDBy^lbz9ww5?aZWmH)9RFmV@!YCq%{ja1J+Bc(KHG$_sTA{t zUl*B`nzI>^<#r{`!Dt(w&_xTFjx^5jz%G~?^hMc6t3@&(9aV3E&ub={6T3*jXz{&irz#?ft^87jK#|u4|VTdv*!ss3dXk@ zdxDZ_dkzSidu1^WvyV`e(S9}17dkdJ(N$GUV6J7Z{)f5pAm%NRhN39AH%-hn%CeeHPMdLgs0 zuu$%(5r=+paq&7F0W2$y%Wk1CaKI>BSV z3NFA69;mZJ^-N8JL53M$8^yL4$NB(WuNas9#=1Br=;ZnLQmx5Xo)5+*CP5&})bhMDY`{$Sdj`Zd7r_n@S$5LcFRsRVR9;rf*wyCFG4jpOZbGRnZMUTM@$Eikv9ciTQq@JIxwmZ-A)Vql=;2Yey~f-}4y?Ay;YC0&|ww zI5apu8TBt zFp!-x@+{cU4J40e2T{h@S3tIbF$eAp0m=XaXEQxLeYPncaKZZe`bzYS|2Hd}z?yuj z1h=3D$Awqu$d(gwUY8+3%2Gs}FexuOD2n2dw3c=1jkFCKBG(f zp0+*za5NiXz;Aybf*MNF^J_^nzE=4Zr5KVH4*vChh` z9o?|!J{Ykv`cpfkC{_XBzyP)@jno7c{_{%>ha#vmoHC3OJ7*q<-(w)2NojzC{?a1BFj~JAJkn&tf{cOhJ=kI2u=v3{q~NI$<1G% zUuQMJXp@EbfQB-i$$5q6>o3zoN9M32j=hs%_(%|0zAS61|g=jkH_L59Zf zB~y**wJ&Fkg3F(s-^>~RWh*tBT>FBfybwWO`8VVk^S$V-3>+2-0{zTS*ntS@t{FxP zWJ&fw=(n)KET{Zh(FDn8h84$J;Xo2|afvf0vHoD)n#_}IeZD_@mBP9DUwK@4d(?iC zkUFO57!y*$E%?UBw(GSK z5c5TDW1LXF)f^dXC9o863b*)LA%9v8gMgbt=yObMaw)C`;e*G)>P+@Spe=GqKhQr; zM-sPRuiBz*t0avTa63T4r6uvSJgyXhr7p1}idWU68Qz>%YmPj>O(lqKE1)cv{T6&M z$U|V&N(*|{L<>qq8$#fSX1(3`@7O;BNfOtwq(;NK&DUW)%7)}I{qhZZK*V#QB8s@;^oU04vfC3W{>*#B464yn9adBci}%oF8G^C%uus=5TB2L2Fqkdnn@U3N(Ja z=#j;-P>K_ZH!FokN7dqv(}?LEB%!pdX1dT-Dy0U5@Eks1StuWPevgm83l`2c*qmuz zpK*oG&{VIn>)!pzo$_sZAno=;9vQ! zaOv(L{GJF z;>2FVsg5oC;1v$-xFhz$8-XfGPjMvzo^HtA zHY#{~D8L4Njm(~jLWm+dNzC^mS=0A!=Ul|7QWjDhl8^Rbq>RIkH!1Z04Xm19cdSb(Pna+_!5Dk_mOyQR|Me@miM+ zuYN_zeTt7{ipO1(GwVPT+Cs=(|ES}vGDVv!AYkQxMEBSZy)4?CtQ}{jWd|WiNbk}0 zG;XAVGnoS!&i77?_xR%$cQaK3hkz1IMcRp3KOm<-cI2D3JkB(<_n~^-3IWKGpl+@y z5wLa&)lh^{Ji(l%Z=<~QS|uyI`Ps7SiW=&_QtW1WZrN*GL1jK5tR%_uj(1RRhH)fR z)&Aiyk;+?6jY~`j+4ZyTC)YDQM1DzLXGAm@E8^tJ?3Bv>j&l&ys9(_%aF)?euelIyTj^z=m%8kRr=O2FeBwVUo10XS2x z-tvo}7LUhhMG^1(PT+HDbjtPd2>Zy?uct=EWtyaC-8yc$mKw|+b8`=^w7G#nWNMeM z16DU*(v&xxf&?v9YsmY@&|sekYiZVM1(;#^-`8cMa6q=4)^yoXXt}{99?+e*CCY+i zkU8-K88J?%hjVax1Scv|+(B}P0G^T@3_`P(kB?Ty8iO##XN0Y_2tfm4mD>~S@lZUc zzml%znGM*VOFB=Y8vk;PB12MmL!dDnmE+WEPnI&I>hWn4hgh9h~vo|S} z+8(X7;^;*pM)EmGWRbBn?d!QPy+3L8B%-i{@Z7)Xd*dzzub|BPr8^dG7*oO2eW5N5 z+hKFgmOSses^mhVUrcEbO@MItFt=Rzm9SDwXaepvC2CPs1Y)d2`-yt>wx0v&A!H^+e9NoltEtYEDyj;;plkSx`80gNHJjijP(y&y)%0dn*;*q%J0MtM zhDH%N2|Jyml5}X2-djLX@3A(5DK#wO%zX`ctm6g(IKY(y&P=4&qdrJX6tGdlV2GCF zU_U78)F6DcO68=iEXPYN?kz)55Lv5|l#x=64&)%oWR=@C$?TsFlm0%W^oA;0tf(p~ z$$*JvP-Etucfa9fTyGVG3*~aFd#Kl{w^n2&%3Scuvx@vjbmduL^JHvTZ5~)jsy&ph z>mV6|KW_u2s{uqf54)20ksyO?#PBc`7uX13lG^}e4`de!&YzN>tkpj$#jO)&)4g%{ zFqlv&7^R{@=WZ+3@S^?JY7q;li8F3e_ADKdik0`N>;h%#=PDda%Mcl1 z@DVZ+?T?>RL)30|NBb!X9a-a^c$n2P{8NW%ENn^_(8YHtWbYP}?j>EGOq@!I_|T+bShK@emuN?^#f*FwZ_BKRb`Hrz7QL9w@#f(`^6pUcmEYtU1fVJvkpi#$Z-yaJ@q*E zv!#U=2DC+2cBN&JPA%V_9je2oEyD-}`cbhXq>HBS)syeK`9fCoxHM46pOtdgX|1c? z-K#V#kRUm(4@9J7(uE1x#8Qm8JBInuc9>6jX~8sT${hw&7VsRn)LDv$YFFmSFeXJM zl>U=Ohf!1n?`+c?BtZu04KT}I-;AK;ekKB(pCP+{d#3f})Chza*t(SL1nhDT> zBX7lXWYNzMfbFV+jV;fJt^VYI>bPF8FdnxoK&I zSn#F8UO^Mj=Jg70i#%+9B1o&N8Vg@HmSW$wgX&uY21p=g$o64=w`rSe=dVYA_a2I% z((&dDRlLV4Gq7aSG25mL#?lH(y6T5pxZ{Lb`e1`0O}@YgY(fh0_nGAA?x2vR!u$sb zAw`3m7T-w$wYis9CUd|6{C}9BdX*9&^N?dg?GI}JHFUQK zT+$rIxM!D#*W`oY#kYBJAfs+es8mjW_IpeXWIiB@83WUL8Wg{cxk9v3HG_8r@lMk{AF7k~SY>ZKj&SptcKFLj;- zd0~MyEBlzrNtF~LI;0V#6-^n$-d2N+{NRrgdv=KF2dnk_a1cy>vD%31hGpOH)t(({ zmv&=yP_CN--43Cb7i%4`m`!?;dCu^@jM`p{#puwJ#GeoYR?*ja51f`7Knaclu<5-_DD5%3OI34hv-kB7>g63ErRAJ@lcLZ*6yaC7$WynPoy0zHV4eo=s=<3$Br6b*BZN!64 z=({a@m(6V$hsd+g`G2Tj4q&N2z8X3wyHe|?SpfB?{P}04&g0h!j=+Etng`q_*n5@) ztTAMO*H9AQ>zUWk0I`fces^Jyp7z8$@HGAYU8ZP@%7V`r|9zVJ9^meM2o&jqTMo2u zz5y9VC|aZ?B3}Wv$S_%;{guD-q8MYsdUh)8iZ6QW_MZ3>b5XJ?FOh6D*`d zA_p%wsvF0CC$!5}z$?K@wOXF@4@shabJ@Sk9|U0o z>Gk+nn<5F%*|FhFd1Nzu3WM4leGk038X*HNW+Q)2r99|77OyZEi~u{#+}=c_2_-4^ zQyH6M=Me!m&=!s>+vUd$O*z|&%0Q&*Kf{iRVf64WRCXKIKh8f;!FqT8^Q!<#aK>QX5JB%7wS{EciT&EDew7N2( z`kdOiH5;z*rr!8~Y_ax`X1zWO**Ax>Fz5H;uqhf&tG3J&*iZ6E8u7Q&U!Gt6S)oy4 z<)-A{`79Rpu~wdys)sCqVmw2-aGe53b zU;7*UT5{%8^@a$N4exBSj)wD1xM+F5n=p3%xMQoKLqtUU&rAluWBoJ}(-v$B%F2)4 z!GF939;ev>5R<%uLEn26zoiH3#vyEPn*?qL4U3LYSFtr4E8sK1;$L%5;d$l$4u{{G z`f)w?*c(GRb+#1Jy7C{3=arM=|8&o zqxb;J@%Fj*FAyLN_f+#Ng^B?A*?(?yH^3QVybrBr%HOCdB1KLOPm$a6i;yC3an_nrhb+z4zA5# z;!fYYdVmLe!@bge_pr2QjSWv{DY&+#Wlz+9{%6o$LVyxC7#mG>OBK=F!XZ49#-;Y+ zT_p@D4fi&7gW`Y$AMm0ED`LA2vHTb4!0t}F;J&^*(o0Uj&?92#zv)Hd_Ytt=x_c3)nET}Auq8*l5Kqn|sn%C*Dz zY4y02FTHsI7c=Lql@p@y`0zRo`&%|J2)2es!Dybog!nw7Hy1*w!vpW1DfjE;!Jkf8 zvMfsDTm7wgr@%nL|D3Y_36E6LFZJg+6v?sTT>;Pcf5MZwpo?p$U}nF=qLiL!Lx7LhxTj$a9nkaX10Pz?eSs&p{Z`}70px#RfdUOg7WT?F~eK^MW$R$WnyQo@;2t zZB!6GTfBR+s#_5qD_?O?3}blcXP#Kdbgmy#G!3(w_VI1&x)Shdc=ul#0wSt1TRecW z2P6Ujt_|p5Jopp^h8uek03=C zYxeL0UPI#7^D2kl_WBak$XVEMdN1}ri%MHsQiTDrMEU<}x^VFE%>x4R@9$j!#2y!r z76K9tz9IF5!=S{rGu8~HR?M;w>YrO#LE8SMF6gwrU6#kaJ zZ#*YwM8ILq`pC6szP)jm2SH$pQIfebq(-gl%cd7bzO)Bq>`fWG{C9n%(K!1R}!bz0c{G37n(QSQ1&*KT-G(-UAzyH>#qh5AOB|B zq}HZS5L?HbjUyOrEF{*Peg!Qid{zZ89I$~nrX!1~?rZ+Z5bFg+4x+ITtm*cIgZU4D z=hV0$j39HM8ICe^MVpyUU2&t}e3T(CGy$^@PeqRz(0 zi~hgturd|TFfox+;#Kl@g-oygj`RBPPMSldAsO*~P%ZWQ&Ovmqf8y_UOdmO?HY1CA z$^WrCel-Bvl1dT;koX)~D)hh)n+te~@{)z))6ih?+gz50cSTma>*xi>w#yI}NhR+S z%R&Po@zJGFan~%vb@6|2=-rM%p9T+f>^;9K{i@MqDj@C?NOD7yNN7qqOf4lrs}!~2 z>RrXd@q@lg*%4^Lf7v%Rj@*q>RX?i8$m&FtsHOg-OBg@MJEauHNiom~vHWH^ZMUqX zm#^PV`2l0(pFt~x;#qSh5WawpFluBi;CKdos1eE%fQ{}2tYYDym|sE%HPLU86_3KH z2}ra01Enz*yL7@jmZ%;92b0f*+~Sd5ZXosTL>38&>9PiH-mxPPDYO=5gtAK8jzFMR ziKsSz{v>jM_YtW7f>4XrYv~1@5aIQ}u}rHi>M>}MnPg{Y;l4ggP4?1BocM<4C42v3 z`PIItoSrjuYi9~ikyJ=A@c$CI;wk%O9gBPE^WnH3edd^1Db@1*z3r?fPI`U`Q!?;S z))DlFS~PIV+*b0;#9&MI+k5WU>tZUaWG%CFGuVhy#*~fg3+Y#K)QK8JJd47Db>Dp$W&ZB+*F`6~6&pRyuC$z8f&#_x%*YCa zrw9lpP3H*>c?kjox>v$|PnbpCk}A3K6+=~~E4U%SM3-RvD_cN!NpjXshLDla2MCH> zf{>DwVZT1Zj?Vgc)zK7R9NYc;&Koxq_l&}8c@*58e)ULJrhNj7?M5BG;!?#8p@QH7 z&O5pWbw50SlC0*yDQERRH$O@zI9uq&{+!-ahl}fpSSzEYzhFX&2RU=D(jp< z0>gzkk;5Ou_>u=3F5rSI!fHe2nAUaRXTxOX4`}UMn4x#KYT~&9O$3+yJ=LsBT2p@x z)%}JG4mNmLt1_i9-F4qlea6|*?6rM*N#8L(O9bk5GoGMa_Fuv*e$`bWry<`oQZ(yP z?~7HR(tW8>LJ#%7F`kx25G1Zg#7QIOo>MC#zVVbuFYv90?Z0_Opz~;};`h=VvL8SJ(_#l7mf-5^LkLB8J`^Cv9 zO9SB#twdJl1ft4n|14sE9V6g(90#K*U{Yx3^=_>=0jvd5*Js)i3QN;kIB1LjqNv=Ew znGEnt<4{!x9PAK%98mmr&tN=7hSaE~62^r{c}Axym#Qk5vHFnF%kY%g?pkjBIH{3} zqTOwvH+G@=mj|6vJH(^5Z82gM=ur|%rd+hIU5{y}e-CPc9t)cc@70@L<`4Fiq)no2 z>l>SHsP*!7tdhR937fHb$L4b=7}G{FVSmy-RR@fXX(y5)>Y9u}cN11K^U*J00iIZj zHr5LP!|GwvIId|-pKH>vSw6K5p2=x8S=(KwT=KH{E)a88!?~fx^u{nFnErv1G2|W6 zq0ShRLP$pH2C_--82-!sRHj$vEyylzSU)kphzUBc`Tzz?2<+4S$B6^}to`oEyywj; z{xi7Uso9@pl?!IwSm7;eyuHoIOR=Yuwo!0wM9^QDhCgWyW8LR|5@93afa)z+R@%o2 zibNqy8YJLZ!`IH((!J0{h+rZCjJt&!EeB#5=9m-SFT5kR=Im9&yW0HXO*vhHswxHz zNkDpa<+RM*8V8c0oRNj3J7_nL zw1GkgC?-qJXr(geSo4Lfo>GMAKKj%q-CHp1@V=eJr6PkfA)Hm>C=Do$?k3I=(Xr^d z*QWrLWVQC+l&m)R=~F7jW7Yfdn2`#afWL9O?D!rRaB1H+G;Qgx1ih-C2gmVXdLfB_jEt9pcYC;s10D_czR+Elp-cu7E^ZD6wH=5D=jUxcxYquW#%( zJAo0GxUO7iE7Z3m9AvJ zH0d0Oty+UHMb5OpTM*t|B5lwHFiaCVORvoMkhjLvudhl&(w!G*^NgS_)mj#&9VAuf zoI?LR^zNQ~hyF0hzkfRsz9l*}{O_0j?@`O36v|$@!|F55gaoHz0qLc}52vT-ZG=0^ zec_rxr&zZXAaCOEf?h#%{@HP^&imS(1gzMHh7D|wcI5L`hF{5W3>1-D<5uvj#DCwWO4?V1Yw%5dA8t5zr(zE%B z{)7ar8VIek+&8CpTPDr=Qa?_E^tBUp#&oC+S8m=y0?i;aOZ6gop35=0F1<{Vmu zaY#I?&7BbvT`%jZ)k}?B0fm);g}3c)CE;POlg16=L3*Teo^_{RiC9tc1M3B}`6IX0 ztcG%8rKvMj-Dsb*;VtM17D@Pm7Q`+%uT?7|IC8RhjW?nqgw2qGjz$_s7HKupl*2Df z7?PCxFnJeNmz+pm0WT;3v7;Ufnzq?`9zeGn6Usx78_`r0ZD<-g*XX=?98r5_`|YK8 zxHIOclWIqH$sT(hf}p7GBg{VvmYeJk<`OmHv0b2yN>cbyt#Kr1G9Inb;rgl6C8cdgvK{6LJPNWA(>$j@NzLGh+w@?6+Kgy6@KemxH8qDx>FFj)xTzNIc1fbU(1q21AB_ z#%}8h!1hD3@5k)7`YL8g=X^U8e#JNU(Ra#!x#Qo{&mA%@21X(Zwk=^- z!9#8<)oQi$Q%m>W2bW*7#?2Qk>s#x$Tn$CbS~UK~ayrbd3_SZISqp!TGy(D?p0sL5 ziBt^9Fp8MS^)9EUf$f`KX6wHX6W(|>VddR0JAt>?x1t3XTZvFNJ=UKE-f#Wk0xAD6 z-|5W_>iVVvTsp6jvu|gI9DseeEcsedqT#eSGES#235ul*vG~Fbkg2U>L^7f)>XKo> zk0=@fqAOVp8j$gxlV*x8{q3F&_J6D(q5UYyX>nxX-_g?Y>(qqb%7GO*w)k$16sOfO zais13=G@X)eJE7&E(0LgT9C@d;cIcNN>>GSNIlmi0?P$6ts#Lx)^xDiEJncC*u`ex57GRYwI(+c)mft921RV zP$lKq(POiTXY*L4?VSHjQCoz&OsD&`tZVmI=jgmu@9Qb|D7g@2o7*G>M{lm-m*d`4 zlnT}gxa=DgtNJtKF;==hi(%+ntw{?Kx}b|Op&qFZDntoMTAEh1knD^$e>vvBr~h|+ zj`R0**R^yUSHdXN;3DN=A1#(Ntx|iM3N=A+S)V&~}$eVwe4Q{ip z2A-0HH2lK{;T_#U-~OaG1zo15aDP=D@3QMu3&vj2=5-zpax8Ncxdc_D1oT;C5kX_% zt>Mg5Q@<7s1qxR>&7CsIlqDt2!a;TubV3CzjE9dbh3t7xOv%t1oe)9PWu|0H6)<3zI z1x4}xQ;;SQi3j}&rlEHy%_P9Xu)apVu_uqO^(=j#_&$@ifw5n%(an>VCIUwWt0pG~ zn~K2=Ik6?ly((Ns61OD*85PqE;rh!FBe^7L_lcpL9G*YiiiD6KwHiDf2Q~ytmYYzE?Oob;uxK~g*K~%5btbGM@ z5vX;0RQnfgUi)u} zqnt>*KRi`UeThezTyJB#3qHhta4jTDRq3bUq!)DB+F@b=SQ2R7VwpOEb&Qw{4(>Zz znd9+Zvno&aFxe@>ezq*#F5H+&&Gi$;54%1U(8%(ubE~jGx(l_ zFS`fhbxI%;$b>4^lAwqm`))bvijJMq;%R~zTzq+kb{Dy-sn5mdVvsBbzH-(>?6KqE*keg0~ZTC0z!8=Clo` z9L-dmTej&$W9F!Sbum+2Vd@h=7S4$}&E#kQp3;SMr!v>%uDtgff$C3Mr{8Al8R{}h zZCcmp*dhFOb%E)S@Q?F3nL+l!BCw|U3QvSfa5))SP*oXQCz zF_<#O$Z*?iiWveJjoEqbnx7q=T?GRLV_5l&!(CLS+KVDE$HFFfQ$4EEL>xPzq7)@q zFmZ~L2AGB4OAQGBUGZ&Kd!JO{btJK53QUJ?k0eyyXbB-W5&&a{DNio7eOm5}b>Nts z#xn=6AlE3nKIN!?MaN`0OFN z{HITF^@-x|exbL~hzb>ArD0hT6iIFU5A(S}{`^aR|8Mon6rpIZ>^L0VwmFzLcplfm zU=~pcaf&-siz2o>k7G~xDm(@IEC{cENcvCqHNLGD=xs4|7281r%*v@L3tC&z@tXuD z_(Usnk0E$U?fARRw0(T+1OK&y(b6NghDYI@)A6s6*elkDSVKo%X|n=P{V(^rIk%c8 zcC5Zf{rk>egTZ1OPzsBYK?uM=8?a@HX1+U+6jO94{Tgn&`2@VDEpwb<3d`e7Hce2H z2sEV1W|tX^@E0MvAXH&H6wmb*y3nvDDP4ae4S{CFP-0Pf5Qy%|M(_D{<`N8zz#%n5 zH($(IhLLm2*E;&U@VD6{rt$>RmmcDM@XsQ1?-L_7uK_{DdowcJLuo}U{Izb4FWj{Y z>bv7g`cBJz20ORojn|+KbDN1H4fx}=PmuZKTIcn2eHn7Skw0J&Ea%Es!&pgdw6;r7 zLlGIIwwAIRf*O6A0vlv0q7xmKjqFPy>UDQVo;0h2$;;_e0}%$=^50KHm$+0TWyEMo zZbl3=UHo9bZqe_a;qPYfr|QbTwMb?P&IT$RCAn4Kl0Wf`tV{hpL8eS2u~WN;?`wZO zRp3-oP2}1kA{FzL69E5U^*9^r`Xw>K$6WTE=0+4(*m3H*3}Z&cF@u&18%J?3k@BjO z@d>mUAuE05m&8dB45cZK3W7ENmq?!qnt?&u$7@tAu=!7H@|tLuRTR*~L2!8pd2JDIERGX~>riMH?IIgGBiIzM( zY>^0%oVsVP;cktvP4$Bopi@K$RGPVDX?EGm@A=S%e~LE}q-BLQcF8P}GcR21a0~NL z11?*SCh=JewS_2l28WK&uVnWoeBM#YYI>VzUa%2Fk5mx>Tg3B(!kUpaWnU|%bAQ-_ z=_|QLR#BJw9BSLl+FWwv2-zLW)QIV0p~r|VnDB6NEwbdzytjb?Xu<-;U6E$7k@*MiN>m7?show|#afl>aBkTybz)0mA{3M;zp<9unvMG*cwvHz1WW(RDFuI-?ndSVxsxxd|xXUSX7sV@XQVz(+*Und1m4-7RHWD|qTfEOW? zwnlFKRus&3!7GmRU*WN@ZSf=YULbktV+Yl_3zNYFT3w#z&=px#dSxD)04uBTtKTD= z07Z`-o|izuQ$Q!d?I_VQ&9x$Z0)j<&hYKFN2XGB^tB8NOYzlc;<8n2ol3fEc8ZU8l zve_0En9`glED(b_VU!icTHq{+{m^U2sML;2U1!2y0_N-CAw#iiF|;Sq4H$yJgAzv;#t6rj}EFX zLd*hI&ZX%jMU+?^mXgxy=Q?d6UF}YafM%6TqkFKXV zHFrktE$;$py3!>*X-Y0?DlqB%oy6CKJs{C+Mjg|}u%Qjs~Ag6wCD|j63js zi0^mzj5mx0^1^hI_s${U4u>vvo=N3Fk+D~*H<$k`9hR=MH6r7(IcPhuder!4FDtBd z5CIXYI2qC`+U2NJS!x>u>)`6Wzk0{9qpHQ&uJFL`Y5@G2PY$mmC zx$Q3#vn*Mw23NP-*YO^Do?)Femmd4K&$wwBVKB zjs6PnMy8kb~BZ3osi8rv*WU26$4v`MkYr7=yzMe$*=Pe&bPJ)wPA==>p|21 zGI8UEUL-Exuv<*$1lw3=g$`s48_R@K*S=7(0TyTiLBv28-GI5KXeH1R;DT8YuSI$c zeMmI|FeSTHQ1Xqi`dW+a(2Wc=zTlb|<&1Px(W$WY`ETulS?(dE-H1<9Ljm;VzixdA zmC(4Wv4K?dh*g(&M&p@7y<@2!Qe7X@6wtC8{L^J1Xg7J~_3$(FjcLsZLds}dhff$e zIYz`gpyM9abgKYm5ZC27%^&I{tozhD1lB#d%uqDu9Ng?q{9Jz5wcRmU74G=06k@N~ zA<$Ldh$@ana1pMoFlZRal8Ks7PI~%tJyI=4RyS%C1?<=SS|-2mGZJ|yvFyiBk{F8J z?-lOwPU)0cs}O5(E>^k^OBsp(+^CKE(0Rxv29CfJs%}kLdZLF$B_=AAGLXgNTYn5& z9CJD?8(Yr`w`oS_Bj<}MHhj>kELk7&?}&eojt-dv`;knSw(a4?kIa@TG;TgpG;&?6 zF?Ow>J;$oaJINl&e9nI8sFcD?q|$-n{S1fnG9sdu=J|v%%m3bvi(Nab0km-0H8Me5 z)IOe~4^z28PrU`{tfR&3L(oKa zDB1$K7GJw%+DJ#svAma|A0|~?3cr>owsK_SBuIKJG;uSH`eJdTT?1q4EkXZ^p9~pG z%-L-qFq?RRJF8t-#niRzaj(0-S9(=qe>Go|ahu`P+OJD&_)d`mP9*86?SMB>-;6cO zsw*fd)Dk~53h^0hxE{5oIwoD&ESn9L+1&HvA9_Lsa_eUPvEMTV{H`xpF8`NA&#FXD zc9!!_9|FXm<89{{)SIUvd9666w9FcW1In}u1C<=f|Sug~qwC{+oPN7SIg4BIoX%)>AibfoH z;3{Pu=M|Q`Q19Oetz@+DQ{&3Od0A!*X^f*<5K-oZc^Rge}P5fc@m3(f$X}LQ=m>pai0>bQ%(erDCD&{i( zxVphYfVcEN^WjQMlD@pa+~BKtqT1@W@>9#`q_C7sWOQf7=}<6=j;C&UwZ5ko-_WzF zJR|CbmSf7rF^Y0>sef;$qxEmrK<^tUg%IU@L9U~_o|G#d4a>HB|?cGBnAnNjZ?UU3CBX@mMwOu{Aa!Yd(bd%fEChW z49B~I__xR$G>*&_S<(7V%dqHF41kp2YSH-91Ko?FmENhx@&QtdFz9bxOD4;l6lE9! zbu-+s2+9`3P{-<~#tJf|PSSH%&Cy|2NUH!SYQ|W{xIdcGs_}$uqATbTQ>?L4)oL(7 zH235y8}o3Kp%B6t9N++KD6C&A&K@FW4vk0Wi1w!oo{Eyi-ukQmFX37F`X^`)4CsLz0SiaYA>%m&`PQ*@e z%Vy(Jkjm0a4^Mj-SVum1vdHS`60pabQ}8bSbB)UTt7AXIzV}^$w7Yh$*f`|bYc5gk zx?tc&d9a04lRMS(XlC_0k+vEBtuxP>p1MjrbCJ8IEJX<}%v9l#8)|0Q*4k~us-io! zOf0G2wCIZ!kp)jjGLd!m*C>xHef+{uCm(Qv7-`sye~fla)#_4^I?HRB7?5JPSHRRb#^7x7m-sU%sMM^Lt{P}9y4e*`J;4> zBJl(p1)hA?l!*9)FzF>Y%>OSjH4r?3edJ*jWGRfZ^bW$|fm}B<}pl^GSFL{R5R00Rc6hgdIr3 z$levg3cH))3;LxU;L#?w9Jg7~?muQ)c8!m9zSI#aUQ#_Dx}|E%nox`TjHiM^Yo=%$ zv5ZZ3dq?ej0_(JYM*{;^_9PO!k)BYiBuD&*q&wD*RNd*IemCU>Sz(3A>KUO6+p%S@ zhf@+*^6!+WLa%_?RJ(3jWtL}*aX1jDSu>)h#*O=V;K0E^}ZN0oahHwr?-k6*D-6IlqlozK>aGfR)zWZ~he_Cqcx8>`8 z+MnQO>BOJhb#WQ$idRs4;c$*N%T4GSpUa5%9!@~(p#L0KjV@vIHPC^T}p@ zF8;(fLeXH5A9WTm)O>TQ_JpNlr@*GpKVg4-s}g(C$u^7vin*^p=&jO_QFoRefrXgo zQmi=Mgt!k=OXw~?duYZp^8Ehm=o%Ky3Tw-Q6|tATkZ*NNd~pgmK~LQy+?U)AIl~yR zxoXJiOCH2nu&pOFpC?XH1-(7|e?+}3Z%u)|mDd_&OU5GrdPQmoWfFzCwhrs+!PST< z(7bL571hpS&~o3nBmhV2DYZLi%{vkD3j7Q7E;*F3fH{xj?}j6>rSc67@{w?&eK}vJ z$U{M)RA05MAdPF8Tkdaqu`UOcONi=;##=T7?r4yUHQqp*x5;aDWD|{9z1(^C`B>1nX(byce zlAH=7S|erOi)Gx*k3?a>)pdBJ!ic=HKR2S+v-~1R|AHu`SJLLawpJ!fDNdwvBfF41 z<0PL5&xwN8Dv4@((5e zmM1>fqaJbuOTf}T-KHDt!;^u)VYxnzk$36W0zslyG!$Atm(ow!BRGvSrZ#;vKHciCZQ%#et{d;2nQ9h|(gX;S>s@n}ocr5Ju zPK%FF#DZs$E*UIYvXzcyjc);d2D<{6lG&GMH818{Rq&3n(;F{Jmsc|4upzs8&zW!UJc_!#6yuRmDj=A8Sk4TOySL%h zyY;-H0r7mAW6sH%<1Vi{<}Mg&s`{xlN&Z4+q!Xiw5 zw_gnJ*q!8rsGyYTj8FffTO$V=lUE)4q%IZjk}S*@?y6``e?D~zJjXCZs}Y|J4gDCh zxnLq7?=bfY;h>D5Q_#ED)~PqP242(ggDZe=L-1A9^ngEQ_u_gXxfuULN1%8C@C@0l z{`*L`Qv%jzIfhD_?>8wRk@ll=QAO;$aBYU7iakbv- zZf@f^=r!gE6t`w%-U*=I+?fYg1otR;lM$)XGBP-C^&C#ko4IB4xd#=8+RqEMzRCzW z(d$LN**hELz};EYVr~S&mzP0Pb6Z#orE_d4_uTV?ELB~V&a-vRFGp8b#}Yldf1G!0 zwUh%%O0o|8-;W;*VVpJOPjJ&EZArg20;~C=8OsP}ST&>lRE9Ym$`K~lebuy-*QT3Z z-{7=O;NX|qouo)8($aUtg?i!-j)LsI@K#I`Tf3$s$B|P3`fEdEcXO_|KRLiGzg|#e zYBsg={oEBwRtJ}Wyyths@cVQ&K-##u$7P&goYK#!!ZbRqG5V2WA?!gHLRT&@X7DY} z4<$^XM(>Fu_P2K0^9yZ0LcUlLTL`#kN93Guz5rv~BVlMdv1ojM0hm>fiXRcP1BwI@ zqi&UwD(|W-X^VNI6Kzy8B6b!=QV!Yel^KO+Nb0iTQ?EHi{Z$gmw)0;J3URQSK5QlI zph1gcjBgFUw|e9fb!a{yUcIZIB8d1ZB*mr8i>Hf%uwOm*&8xphGfr@|BINqxdi-&7 z*w@C9tL4Sw$CZspf2c%NTrsY|!l31-1PD6o8sEgX{d%D|ZxHhr)tAa-*6jS^Il_uH zLI5w)5jzi97=s+U&3t>O#JbS#G2ok3xFkUAX)jplfqBguG%h9U-8@i@k7_gnJH~qh zxtmj2U3$rchfbxmMrvh#MhTyU1sIluD&)GBUjBeTa%5pYsndl-mxq(}mc!XNfBekl zmfp}e?cf|a9(RAYxp8qwJvMpx55=5Z|9tvBVsQ=Xz}_+xxh#3LZ?ny3v|fy=rBiKe z)#;U*G0wrh%X_iK9oh+F08tnRIf9rD9S)q%MsoD{=zowt6j(@GtExUlHnVC; zm!w8bA3pp=eH_Bku&1aGu{*%8wsf2Ht>e++N}$q_R4h=|pMMBtDrE9OwlTx#*yrui zvE)G2G5sI^9o!U=UYX=b(s~q?UHMDkAfTFupy+b17{4}0pn73TS)T!*;in$+^{Bc( ztNKPNn)FrasA(}VF(gkm5tbLCIcoN)Xv z~HXZXxez2~Z zBN8|!gz=yOP_Kyl3px@yTm22f%YXS3tdUrA`D7NSOyTUMyKKJ5RAa4lN^mu7BY|g$ zZ8a`FX;OP$tA>CaW+9QlH0H7-#Epb#m6XKN_;rdjL>9{8p!Sr;=7uvoi~^OEI*cl- zU}}bk1Rg60{IhCSndv*gQy<{T60moaZISNX-s4IMH8e|c>iizwd^Bh(v>5ky?A30F zYz-fXVRPV%%_S~bC>2GAk0#wW-QOEUjdo;YXqGX-r)@P$Nlpq#OA(19lv0$_O&`}}+ z@>s|2qkPabFQhe@ZyfCSjbdF)(qi)+>g3#-EHXto7ra)rr#u5MS+A`NOcPtM`@H+X z;H!t#;Z9Y{rJ`-1_?%UTAa`A%)GD;`j(fkodx_0?JVW|=<6E9cfw~9bQ^H%CY4ehN zpdum0qWi#KmPTCc{6JE%uUV8PP|uW5*lq#Net85fXny#c;#7lW?E+ngOM{~t(a1}G z`7}YJ%NiN-Bx-P$O#aQ8RKYNovDKy4iHe4EGw^P>wqq)2cf{K8$5!o z_^<&unAtT&<~>4VwcNk*gROeC89{2RC)4*teVG-??6BKS&QzWX%pUJlKPQ^NKJWhs z)#|W~c`4##xb(acbTrGX=!?UMk_e_*A+0GK>d}5i{26%8?#5IK&QrvhOd)xIv51$6 zdQHXtD?`JIIvK(OcUKBkf&gPFLIEE3Q~i*fs{zc+v~)BU1C=c;qB{6d2`47+5tFO2 zjy#P-#KZ*G>!TMd)6^UedWnTKA~LojAq@dqo@c!>J~Y;L)J!zH>nnJs5=GWCU!xB! za+Lw-$*kk&0K}|A+{2U+3#KaMw)8vs$&=AiD%4ExDbqgdzHxCM;bQxw%|m!BHP8BA z9P*l8Tm>Z)81ezgi;*%zG907o`aj};Fvs^1Ry`Jh7*m2}vrVqrE3EqFILa)j|CU>jEH1ZT^nZ5)MLm6WI5C^p z(QEb-Vwz!GUFcN7H!zxA`IBSEpC+yIG1?#dRH(#GG5BkS+1n82sGEIi0;LFfKW<~t zY^dl5{FVN(F0t1&#+MVv;-GfAZ2bXBZ1c;Fr<-TKXPB_04@s-@CoQL6=k6`6WqFir z_qrrP{n3d>+>(`2Mb+;H^;Tqq&OB+RQOVXM<|qLQ^Uek5OVB8j$*ZVg>|eTF`&^NOW$rWb&s>``O;nFHyh3BrTA&UOBj+J zJ96aaZFh}?faVIOxZBPZP(Nu0KQ0$5Up@`L z)AcG8gk8f!upRUy9wU&+`b?q_`nI``HnM8Uln_``8OzJU@KPwj!SoKnz_f}oSyM)f z-rxBUaj*T2UA27IaHIu391IsD)!ZJocWzMc2jXZsJ~~GzT}t{R3$+#T%6gw=osVA` z?NmeN$w;mIc$}#SM0DDfM4`P0W@}?u{qEfT;XNw3;2x7?k=Ik_>QLLFJ!jE`PNhpC zw0i3%kr`{x65|ZWJ8S8f)TR&j4_X)u1mTy;>+;yhy%eLMqI<hb6Ytpt-S#ITiY+)m)R*5>q?p>M4eh9Q$*;2jVOVLOk(l}5* z(u7U*Dml5d6P>Sad12h{RhyWKol0;@STiq$!^>+;e|Kc!;UcNMl+=ESEuVKZkvvfwpq=AQK+PjuI|+_X7#v zy8SK`xUf__u-hxI)1L-U*eDW0hEoC#_W-q}_;kj6g1?jJVp2^FtI@2r_Y6-8;Ma5c z#sx1e?l8P5T{}^{61A(ob}Bd4)p$$4s>!?%5yEVaJK=FOJeEh*qo$OOhYU03&$eT; zhSp@#GQ6)+r9l{SvlMelX}E7OK-Z;b{1wZswuO)v<~<5EV^6+W?84SSyf!S~83`h@ z|0}eok4gA#Pw|2X)5!AxG1wxVKGAD||Dt|QbtX4PJ76Xi6`Ri|=60CV@cY}A=+V4V z#GEKFE&MojI%TMr#yFswk`PPmFaa(;Q4A0s?1~LeINxUXWkE+=RsC__6Z$h|O3+xs zd_!1Dz4H)>ci*~MGn+8v%C(!*5*i5 zV#u{n?SIYX?7F%s!%LRyf%C-!qPe242}!)FyJZ>&Sc4up;0D`z&Mii)Sd*9TNk!#T zHOG0Ck}$s=!mhl#O&ekE9bB#(qzTp^*E|7viQJ)wekogNmO3>yv|yNe_AcwGa;#fL zfB}s@ANd`~;Q}taBh_BaJ$TQXqd_|CZN8l;0G*&#N;G-+o>X)>54rFiZ4?XCX7ff( z)t^SgH2Q%rT?Qd`Te=wwF@h8;sTdCX`?ahk`RhITs;7f|@vqvqOum}F(bSvS5_NGu z5S}D85oknXrhwAW=#%|@V18h_$_w$GFx8Zj=LbPH!$? zDMaB|8=`TnBgJc2aASPV8MNN18AG??Hjz2n?@|8y4c0!#+Ga)Mb&nJPr7~Ua) z=*cZ*&`KwU@aZ@Bg$75PlKEWc)VpntINAppZKZ&@RG(7rlcWq82xQ5bPckXg{3~Zw zmLI#j4o5fzc>$=U7-fO%4pEb`^}DS!t+9{;R^VajC$uK=#!ey}IHCJGG8sTF)BNl# z7(z+z^uKU(o+1kV`Ijd8lhV<%KmcGPcfgrNW08*-5bR`tO^GpRV8jB*KAVFzfd50g z`YLI7V>@WiF4V-C=%WD4qEzWFtZrvhAL3FawW*_uuY&FuD)$D!K$D*rLB>8yxpfiz zw}r6UN+vU#;-0H;J6kgs(V0h2lh(v^DHv~6_T9I!xCt1b8L?soCWPh&X?q>cJ% z>%>Wy7czMeRA(&2&Rr1Eo|sx~JLJRJy87UIou7EyyD@a7wB~eVEYsqz>omi)4@XL@ zw*}VrUZjfxn!(ju%Wu_e#m+O=gQw3dM{!ib%sR}InPnEfj(0D~F-Snh0y3kFK4uEb z4MinFNr>pz^p#epVnA1@Wdde09IQ5HSR(zsccm8o#GnD;s25^HIa(iDhsS%gDWY8# z@zRl->^&cFyztPrk0hzT#y|%FAeQ|W{B;lJ*9d;&V*axyJvpq|) zrUaiI@>%L#ETy6cadWrBbdlM*kinaU) zci&sW>kPeUCmOAbbKH+Q#0P;VYD2jf4zdN5f}2DwD{p^5O`_=|SJqj$Gyma$p6M)n zvDnkhW%)@&W)ipob6-bVzgRcX&_*;{o@T?+=jsq6Im{(brxBIN7Dq_rg#xwmII7@5 z{ai{6({>?af$$07sHP}enYhYls*WAF!||~AiqVf$eTa#%}#8>kTU&V9<}Loep%8#WgrVb2i?@mOBH zP845eB^K%$ai1f+HBOK$z1g8VuASbe9fW}89W}RR2nVUTrReXgJ@at%%}_L`5@J8IB}0Cxb6-_FD$yl6I={XIc>bjEhLIuJEp*tFr-t z>3|2tnH;Zp|vPZm`T>sxPRlJX7075 zj~K0knavhk0T&itmnTmIEchw{{8CdNMuNRz4Qlg4@gKV$%TrlLa>#tazF04N_qQWb z%qn%s+N$!~`HxjrUP*|Bt{n!DH!4&m9WQ=z|$wyXs{bAKbbF1#9N!F6kYQ5wjWpn+paw)@EH zyg%4Ya4%w>H+i}A2Rxy|flg1Ehi(!9WwI1-ROo(Vzpg>|@AQhHcp~~b^T`8<#Kp4K zYq(X)m5W!N5c*V-%&Qm}afy~U$!na;Kq+<76s=*g!!QQ)DEiG?Q z{*#O4H4-YLG+gx<11hMYEfLNo>iMPwZ zH=Qfz2|dFJbcj8_&sdgU~2aVFi&rnen>`Ip;xu6 z?A#R39D}~g;6|A82)gE`gy61Mln^27ctn=?#j_+#e5T|-vQ(Lk>wm2}=;+sS=P;mO z+m))|2;TGcO9Sgs6Kq^KzhyN`QK;kaVC`Sf zP#SDp1v7l7&E~lcpoR5@>%NwO~i3s?ag4fHk4*yDL2SfCf%HA z@7ab^LxbAxDd<#ntDRX0aucx3oGb*rZgSFBnZ7bCx#aYcAFpUD{r`t&dY6-lVH{Wc zOQv?=S*@c{PV>))YDebf&Evy47$^T5P7KRmPhsMWAy#DPQ{zW;1s2)6Uj%ml{OE7( z(B*q{uu|Hvu>A0bknYeqHc%=h3fJobgT;gtie?8V=O4mV@>fuu5d)p9O=elLyI<)d f9-~x1vHt_#-2605WN>*!1suk(+XmHo&T;<-t;A~I diff --git a/Dependencies/MelonStartScreen/Resources/Logo_Lemon.dat b/Dependencies/MelonStartScreen/Resources/Logo_Lemon.dat deleted file mode 100644 index 4803417e0eb6352d2bba862655e8d15b28bfb056..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42415 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 OkDQdUWR19S=>Gs&R?zwY diff --git a/Dependencies/MelonStartScreen/ScreenRenderer.cs b/Dependencies/MelonStartScreen/ScreenRenderer.cs deleted file mode 100644 index fe930614b..000000000 --- a/Dependencies/MelonStartScreen/ScreenRenderer.cs +++ /dev/null @@ -1,125 +0,0 @@ -using MelonLoader.MelonStartScreen.NativeUtils; -using MelonLoader.MelonStartScreen.UI; -using System; -using System.Reflection; -using MelonUnityEngine.CoreModule; -using UnityPlayer; - -namespace MelonLoader.MelonStartScreen -{ - internal static class ScreenRenderer - { - private const float logoRatio = 1.2353f; - - private delegate void SetupPixelCorrectCoordinates(bool param_1); - -#pragma warning disable 0649 - #region m_SetupPixelCorrectCoordinates Signatures - [NativeSignature(01, NativeSignatureFlags.X86, "55 8b ec 83 ec 60 56 e8 ?? ?? ?? ?? 8b f0 8b 45 08 50 8d 4d f0 51 e8", "2017.1.0")] - [NativeSignature(02, NativeSignatureFlags.X86, "55 8b ec 83 ec 60 53 56 57 e8 ?? ?? ?? ?? ff 75 08 8b d8 8d 45 f0 50 e8", "2017.3.0", "2018.1.0")] - [NativeSignature(03, NativeSignatureFlags.X86, "55 8b ec 83 ec 60 53 56 57 e8 ?? ?? ?? ?? 8b d8 e8 ?? ?? ?? ?? ff 75 08 8d 4d f0", "2019.1.0")] - [NativeSignature(01, NativeSignatureFlags.X64, "48 89 5c 24 08 57 48 81 ec a0 00 00 00 8b d9 e8 ?? ?? ?? ?? 48 8b f8 e8", "2017.1.0")] - #endregion - private static SetupPixelCorrectCoordinates m_SetupPixelCorrectCoordinates; -#pragma warning restore 0649 - - public static bool disabled = false; - - private static uint shouldCallWFLPAGT = 0; - - internal static void Init() - { - if (disabled) - return; - - try - { - MelonDebug.Msg("Initializing UIStyleValues"); - UI_Style.Init(); - MelonDebug.Msg("UIStyleValues Initialized"); - - uint graphicsDeviceType = SystemInfo.GetGraphicsDeviceType(); - MelonDebug.Msg("Graphics Device Type: " + graphicsDeviceType); - shouldCallWFLPAGT = NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new[] { "2020.2.7", "2020.3.0", "2021.1.0" }) - && (graphicsDeviceType == /*DX11*/2 || graphicsDeviceType == /*DX12*/18) - ? graphicsDeviceType : 0; - } - catch (Exception e) - { - Core.Logger.Error("Exception while init rendering: " + e); - disabled = true; - } - } - - internal static unsafe void Render() - { - if (disabled) - return; - - try - { - m_SetupPixelCorrectCoordinates(false); - - UI_Style.Render(); - - GfxDevice.PresentFrame(); - if (shouldCallWFLPAGT != 0) - GfxDevice.WaitForLastPresentationAndGetTimestamp(shouldCallWFLPAGT); - } - catch (Exception e) - { - Core.Logger.Error("Exception while rendering: " + e); - disabled = true; - } - } - - internal static void UpdateMainProgress(string text, float progress) - { - if (UI_Style.ProgressBar == null) - return; - - UI_Style.ProgressBar.text.text = text; - UI_Style.ProgressBar.text.isDirty = true; - UI_Style.ProgressBar.progress = progress; - } - - internal static void UpdateProgressFromLog(string msg) - { - if (UI_Style.ProgressBar == null) - return; - - UI_Style.ProgressBar.progress = ProgressParser.GetProgressFromLog(msg, ref UI_Style.ProgressBar.text.text, UI_Style.ProgressBar.progress); - UI_Style.ProgressBar.text.isDirty = true; - } - - internal static void UpdateProgressFromMod(MelonBase melon) - { - if (UI_Style.ProgressBar == null) - return; - - UI_Style.ProgressBar.progress = ProgressParser.GetProgressFromMod(melon, ref UI_Style.ProgressBar.text.text); - UI_Style.ProgressBar.text.isDirty = true; - } - - internal static void UpdateProgressFromModAssembly(Assembly asm) - { - if (UI_Style.ProgressBar == null) - return; - - UI_Style.ProgressBar.progress = ProgressParser.GetProgressFromModAssembly(asm, ref UI_Style.ProgressBar.text.text); - UI_Style.ProgressBar.text.isDirty = true; - } - - internal static void UpdateProgressState(ModLoadStep step) - { - if (UI_Style.ProgressBar == null) - return; - - if (ProgressParser.SetModState(step, ref UI_Style.ProgressBar.text.text, out float generationPart)) - { - UI_Style.ProgressBar.progress = generationPart; - UI_Style.ProgressBar.text.isDirty = true; - } - } - } -} diff --git a/Dependencies/MelonStartScreen/TextMeshGenerator.cs b/Dependencies/MelonStartScreen/TextMeshGenerator.cs deleted file mode 100644 index e7db0d981..000000000 --- a/Dependencies/MelonStartScreen/TextMeshGenerator.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Linq; -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen -{ - internal static class TextMeshGenerator - { - public static Mesh Generate(string text, TextGenerationSettings settings) - { - TextGenerator generator = new TextGenerator(); - generator.Populate(text, settings); - - Mesh mesh = new Mesh(); - UIVertexWrapper[] vertices = generator.GetVerticesArray(); - Vector3[] verticesPosition = vertices.Select(v => v.position).ToArray(); - mesh.vertices = verticesPosition; - if (GL.sRGBWrite) - mesh.colors = vertices.Select(v => { - Color color = v.color; - color.r = (float)Math.Pow(color.r, 2.2); - color.g = (float)Math.Pow(color.g, 2.2); - color.b = (float)Math.Pow(color.b, 2.2); - return color; - }).ToArray(); - else - mesh.colors = vertices.Select(v => (Color)v.color).ToArray(); - mesh.normals = vertices.Select(v => v.normal).ToArray(); - mesh.tangents = vertices.Select(v => v.tangent).ToArray(); - mesh.uv = vertices.Select(v => v.uv0).ToArray(); - - int characterCount = generator.vertexCount / 4; - int[] indices = new int[characterCount * 6]; - for (var i = 0; i < characterCount; ++i) - { - int vertIndexStart = i * 4; - int trianglesIndexStart = i * 6; - indices[trianglesIndexStart++] = vertIndexStart; - indices[trianglesIndexStart++] = vertIndexStart + 1; - indices[trianglesIndexStart++] = vertIndexStart + 2; - indices[trianglesIndexStart++] = vertIndexStart; - indices[trianglesIndexStart++] = vertIndexStart + 2; - indices[trianglesIndexStart] = vertIndexStart + 3; - } - mesh.triangles = indices; - mesh.RecalculateBounds(); - - return mesh; - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/Objects/UI_AnimatedImage.cs b/Dependencies/MelonStartScreen/UI/Objects/UI_AnimatedImage.cs deleted file mode 100644 index 0131d24c0..000000000 --- a/Dependencies/MelonStartScreen/UI/Objects/UI_AnimatedImage.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI.Objects -{ - internal class UI_AnimatedImage : UI_Image - { - private Stopwatch stopwatch = new Stopwatch(); - private float frameDelayMS = 90f; - private Texture2D[] textures; - - internal UI_AnimatedImage(UI_Theme.ImageSettings imageSettings, string filepath) : base(imageSettings, filepath) { } - internal UI_AnimatedImage(UI_Theme.ImageSettings imageSettings, byte[] filedata) : base(imageSettings, filedata) { } - internal override void LoadImage(UI_Theme.ImageSettings imageSettings, byte[] filedata) - { - config = imageSettings; - - mgGif.Decoder decoder = new mgGif.Decoder(filedata); - - var img = decoder.NextImage(); - frameDelayMS = img.Delay; - - List images = new List(); - while (img != null) - { - Texture2D newtexture = img.CreateTexture(config.Filter); - newtexture.hideFlags = HideFlags.HideAndDontSave; - newtexture.DontDestroyOnLoad(); - - images.Add(newtexture); - img = decoder.NextImage(); - } - - textures = images.ToArray(); - MainTexture = textures[0]; - AspectRatio = MainTexture.width / (float)MainTexture.height; - - AllElements.Add(this); - } - - internal override void Render() - { - if (!config.Enabled || (textures == null) || (textures.Length <= 0)) - { - if (stopwatch.IsRunning) - stopwatch.Stop(); - return; - } - - if (!stopwatch.IsRunning) - stopwatch.Start(); - - int image = (int)((float)(stopwatch.ElapsedMilliseconds / frameDelayMS) % textures.Length); - MainTexture = textures[image]; - - base.Render(); - } - - internal override void Render(int x, int y, int width, int height) - { - if (!config.Enabled) - { - if (stopwatch.IsRunning) - stopwatch.Stop(); - return; - } - - if (!stopwatch.IsRunning) - stopwatch.Start(); - - int image = (int)((float)(stopwatch.ElapsedMilliseconds / frameDelayMS) % textures.Length); - MainTexture = textures[image]; - - base.Render(x, y, width, height); - } - - internal override void Dispose() - { - if ((textures == null) || (textures.Length <= 0)) - return; - - foreach (Texture2D texture in textures) - texture.DestroyImmediate(); - - textures = null; - MainTexture = null; - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/Objects/UI_Background.cs b/Dependencies/MelonStartScreen/UI/Objects/UI_Background.cs deleted file mode 100644 index 37cb565b3..000000000 --- a/Dependencies/MelonStartScreen/UI/Objects/UI_Background.cs +++ /dev/null @@ -1,48 +0,0 @@ -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI.Objects -{ - internal class UI_Background : UI_Object - { - private UI_Theme.cBackground config; - private UI_Image image; - internal Texture2D solidTexture; - - internal UI_Background(UI_Theme.cBackground backgroundSettings) - { - config = backgroundSettings; - image = UI_Utils.LoadImage(config, "Background"); - - solidTexture = UI_Utils.CreateColorTexture(config.SolidColor); - solidTexture.hideFlags = HideFlags.HideAndDontSave; - solidTexture.DontDestroyOnLoad(); - AllElements.Add(this); - } - - internal override void Render() - { - int sw = Screen.width; - int sh = Screen.height; - - if (solidTexture != null) - Graphics.DrawTexture(new Rect(0, 0, sw, sh), solidTexture); - - if (image != null) - { - if (config.StretchToScreen) - image.Render(0, 0, sw, sh); - else - image.Render(); - } - } - - internal override void Dispose() - { - if (solidTexture == null) - return; - - solidTexture.DestroyImmediate(); - solidTexture = null; - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/Objects/UI_Image.cs b/Dependencies/MelonStartScreen/UI/Objects/UI_Image.cs deleted file mode 100644 index 584848022..000000000 --- a/Dependencies/MelonStartScreen/UI/Objects/UI_Image.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.IO; -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI.Objects -{ - internal class UI_Image : UI_Object - { - internal UI_Theme.ImageSettings config; - internal Texture2D MainTexture; - internal float AspectRatio; - - internal UI_Image(UI_Theme.ImageSettings imageSettings, string filepath) => LoadImage(imageSettings, File.ReadAllBytes(filepath)); - internal UI_Image(UI_Theme.ImageSettings imageSettings, byte[] filedata) => LoadImage(imageSettings, filedata); - - internal virtual void LoadImage(UI_Theme.ImageSettings imageSettings, byte[] filedata) - { - config = imageSettings; - - MainTexture = new Texture2D(2, 2); - MainTexture.filterMode = config.Filter; - - if (!ImageConversion.LoadImage(MainTexture, filedata, false)) - throw new Exception("ImageConversion.LoadImage Failed!"); - - AspectRatio = MainTexture.width / (float)MainTexture.height; - - MainTexture.hideFlags = HideFlags.HideAndDontSave; - MainTexture.DontDestroyOnLoad(); - - AllElements.Add(this); - } - - internal override void Render() - { - if (!config.Enabled) - return; - if (MainTexture == null) - return; - - int aspectHeight = config.MaintainAspectRatio ? (int)(config.Size.Item1 / AspectRatio) : config.Size.Item2; - - UI_Utils.AnchorToScreen(config.ScreenAnchor, config.Position.Item1, config.Position.Item2, out int anchor_x, out int anchor_y); - UI_Utils.AnchorToObject(config.Anchor, anchor_x, anchor_y, config.Size.Item1, -aspectHeight, out anchor_x, out anchor_y); - - Graphics.DrawTexture(new Rect(anchor_x, anchor_y, config.Size.Item1, -aspectHeight), MainTexture); - } - - internal virtual void Render(int x, int y, int width, int height) - { - if (!config.Enabled) - return; - Graphics.DrawTexture(new Rect(x, height + y, width, -height), MainTexture); - } - - internal override void Dispose() - { - if (MainTexture == null) - return; - - MainTexture.DestroyImmediate(); - MainTexture = null; - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/Objects/UI_Object.cs b/Dependencies/MelonStartScreen/UI/Objects/UI_Object.cs deleted file mode 100644 index 9f165c1a0..000000000 --- a/Dependencies/MelonStartScreen/UI/Objects/UI_Object.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MelonLoader.MelonStartScreen.UI.Objects -{ - internal abstract class UI_Object : IDisposable - { - internal static List AllElements = new List(); - private bool disposedValue; - - internal virtual void Dispose() { } - internal abstract void Render(); - - protected virtual void Dispose(bool managed) - { - if (disposedValue) - return; - if (managed) - Dispose(); - disposedValue = true; - } - - void IDisposable.Dispose() - { - Dispose(managed: true); - GC.SuppressFinalize(this); - } - - internal static void DisposeOfAll() - { - if (AllElements.Count <= 0) - return; - foreach (UI_Object obj in AllElements) - obj.Dispose(); - AllElements.Clear(); - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/Objects/UI_ProgressBar.cs b/Dependencies/MelonStartScreen/UI/Objects/UI_ProgressBar.cs deleted file mode 100644 index 0dded3547..000000000 --- a/Dependencies/MelonStartScreen/UI/Objects/UI_ProgressBar.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI.Objects -{ - internal class UI_ProgressBar : UI_Object - { - private UI_Theme.cProgressBar config; - internal float progress; - internal UI_Text text; - private Texture2D innerTexture; - private Texture2D outerTexture; - - internal UI_ProgressBar(UI_Theme.cProgressBar progressBarSettings, UI_Theme.TextSettings textSettings) - { - config = progressBarSettings; - text = new UI_Text(textSettings); - - innerTexture = UI_Utils.CreateColorTexture(config.InnerColor); - innerTexture.hideFlags = HideFlags.HideAndDontSave; - innerTexture.DontDestroyOnLoad(); - - outerTexture = UI_Utils.CreateColorTexture(config.OuterColor); - outerTexture.hideFlags = HideFlags.HideAndDontSave; - outerTexture.DontDestroyOnLoad(); - - AllElements.Add(this); - } - - internal override void Render() - { - if (config.Enabled && (outerTexture != null) && (UI_Style.Background.solidTexture != null) && (innerTexture != null)) - { - UI_Utils.AnchorToScreen(config.ScreenAnchor, config.Position.Item1, config.Position.Item2, out int anchor_x, out int anchor_y); - UI_Utils.AnchorToObject(config.Anchor, anchor_x, anchor_y, config.Size.Item1, config.Size.Item2, out anchor_x, out anchor_y); - - Graphics.DrawTexture(new Rect(anchor_x, anchor_y, config.Size.Item1, config.Size.Item2), outerTexture); - Graphics.DrawTexture(new Rect(anchor_x + 6, anchor_y + 6, config.Size.Item1 - 12, config.Size.Item2 - 12), UI_Style.Background.solidTexture); - Graphics.DrawTexture(new Rect(anchor_x + 9, anchor_y + 9, (int)((config.Size.Item1 - 18) * Math.Min(1.0f, progress)), config.Size.Item2 - 18), innerTexture); - } - - text?.Render(); - } - - internal override void Dispose() - { - if (innerTexture != null) - { - innerTexture.DestroyImmediate(); - innerTexture = null; - } - - if (outerTexture != null) - { - outerTexture.DestroyImmediate(); - outerTexture = null; - } - - if (text != null) - text.Dispose(); - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/Objects/UI_Text.cs b/Dependencies/MelonStartScreen/UI/Objects/UI_Text.cs deleted file mode 100644 index d926baea9..000000000 --- a/Dependencies/MelonStartScreen/UI/Objects/UI_Text.cs +++ /dev/null @@ -1,98 +0,0 @@ -using MelonLoader.Properties; -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI.Objects -{ - internal class UI_Text : UI_Object - { - private UI_Theme.TextSettings config; - private Mesh mesh; - internal bool isDirty = true; - internal string text; - internal Font font; - - internal UI_Text(UI_Theme.TextSettings textSettings) - { - config = textSettings; - text = config.Text; - - try - { - font = MelonUnityEngine.Resources.GetBuiltinResource($"{config.Font}.ttf"); - } - catch { } - - if (font == null) - font = MelonUnityEngine.Resources.GetBuiltinResource("Arial.ttf"); - - AllElements.Add(this); - } - - internal override void Render() - => Render(config.Position.Item1, config.Position.Item2); - internal void Render(int x, int y) - { - if (!config.Enabled) - return; - - UpdateMesh(); - font.material.SetPass(0); - if (mesh == null) - return; - - UI_Utils.AnchorToScreen(config.ScreenAnchor, x, y, out int anchor_x, out int anchor_y); - Graphics.DrawMeshNow(mesh, new Vector3(anchor_x, anchor_y, 0), Quaternion.identity); - } - - private void UpdateMesh() - { - if (!isDirty) - return; - if (string.IsNullOrEmpty(text)) - return; - - if (mesh != null) - { - mesh.DestroyImmediate(); - mesh = null; - } - - TextGenerationSettings settings = new TextGenerationSettings(); - settings.generationExtents = new Vector2(540, 47.5f); - settings.pivot = new Vector2(0.5f, 0.5f); - settings.verticalOverflow = VerticalWrapMode.Overflow; - settings.font = font; - settings.textAnchor = (TextAnchor)config.Anchor; - settings.color = config.TextColor; - settings.richText = config.RichText; - settings.fontSize = config.TextSize; - settings.fontStyle = config.Style; - settings.scaleFactor = config.Scale; - settings.lineSpacing = config.LineSpacing; - - string displayText = text; - displayText = displayText.Replace("", (MelonLaunchOptions.Console.Mode == MelonLaunchOptions.Console.DisplayMode.LEMON) ? "LemonLoader" : "MelonLoader"); - displayText = displayText.Replace("", BuildInfo.Version); - displayText = displayText.Replace("LemonLoader", "LemonLoader"); - displayText = displayText.Replace("MelonLoader", "MelonLoader"); - displayText = displayText.Replace("", "MelonLoader"); - - - - mesh = TextMeshGenerator.Generate(displayText, settings); - mesh.hideFlags = HideFlags.HideAndDontSave; - mesh.DontDestroyOnLoad(); - - isDirty = false; - } - - internal override void Dispose() - { - if (mesh == null) - return; - - mesh.DestroyImmediate(); - mesh = null; - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/StartScreenResources.cs b/Dependencies/MelonStartScreen/UI/StartScreenResources.cs deleted file mode 100644 index 38ebcfadc..000000000 --- a/Dependencies/MelonStartScreen/UI/StartScreenResources.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.IO; -using System.Reflection; -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI -{ - internal static class StartScreenResources - { - public static byte[] HalloweenLoadingIcon => GetResource("Loading_Halloween.dat"); - public static byte[] MelonLoadingIcon => GetResource("Loading_Melon.dat"); - public static byte[] LemonLoadingIcon => GetResource("Loading_Lemon.dat"); - - //Logos - public static byte[] HalloweenLogo => GetResource("Logo_Halloween.dat"); - public static byte[] MelonLogo => GetResource("Logo_Melon.dat"); - public static byte[] LemonLogo => GetResource("Logo_Lemon.dat"); - - private static byte[] GetResource(string name) - { - using var s = Assembly.GetExecutingAssembly().GetManifestResourceStream($"MelonLoader.MelonStartScreen.Resources.{name}"); - if (s == null) - return null; -#if NET6_0_OR_GREATER - using var ms = new MemoryStream(); - s.CopyTo(ms); - return ms.ToArray(); -#else - var ret = new byte[s.Length]; - s.Read(ret, 0, ret.Length); - return ret; -#endif - } - } -} \ No newline at end of file diff --git a/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Default.cs b/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Default.cs deleted file mode 100644 index b4890682f..000000000 --- a/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Default.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MelonLoader.MelonStartScreen.UI.Themes -{ - internal class UI_Theme_Default : UI_Theme - { - internal UI_Theme_Default() => Defaults(); - - internal override byte[] GetLoadingImage() - => StartScreenResources.MelonLoadingIcon; - - internal override byte[] GetLogoImage() - => StartScreenResources.MelonLogo; - } -} diff --git a/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Lemon.cs b/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Lemon.cs deleted file mode 100644 index 6e0a614c5..000000000 --- a/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Lemon.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MelonLoader.MelonStartScreen.UI.Themes -{ - internal class UI_Theme_Lemon : UI_Theme - { - internal UI_Theme_Lemon() => Defaults(); - - internal override byte[] GetLoadingImage() - => StartScreenResources.LemonLoadingIcon; - - internal override byte[] GetLogoImage() - => StartScreenResources.LemonLogo; - } -} diff --git a/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Pumpkin.cs b/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Pumpkin.cs deleted file mode 100644 index 52229ce7f..000000000 --- a/Dependencies/MelonStartScreen/UI/Themes/UI_Theme_Pumpkin.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI.Themes -{ - internal class UI_Theme_Pumpkin : UI_Theme - { - internal UI_Theme_Pumpkin() - { - Background = new hBackground(); - ProgressBar = new hProgressBar(); - VersionText = new hVersionTextSettings(); - Defaults(); - } - - internal override byte[] GetLoadingImage() - => StartScreenResources.HalloweenLoadingIcon; - - internal override byte[] GetLogoImage() - => StartScreenResources.HalloweenLogo; - - internal class hBackground : cBackground - { - public hBackground() - => SolidColor = new Color(0.078f, 0f, 0.141f); - } - - internal class hProgressBar : cProgressBar - { - public hProgressBar() : base() - { - OuterColor = new Color(0.478f, 0.169f, 0.749f); - InnerColor = new Color(1f, 0.435f, 0f); - Defaults(); - } - } - - internal class hVersionTextSettings : VersionTextSettings - { - public hVersionTextSettings() - { - Text = $" v {(Is_ALPHA_PreRelease ? "ALPHA Pre-Release" : "Open-Beta")}"; - Defaults(); - } - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/UI_Anchor.cs b/Dependencies/MelonStartScreen/UI/UI_Anchor.cs deleted file mode 100644 index 3a6717082..000000000 --- a/Dependencies/MelonStartScreen/UI/UI_Anchor.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MelonLoader.MelonStartScreen.UI -{ - internal enum UI_Anchor - { - // Upper - UpperLeft, - UpperCenter, - UpperRight, - - // Middle - MiddleLeft, - MiddleCenter, - MiddleRight, - - // Lower - LowerLeft, - LowerCenter, - LowerRight - } -} diff --git a/Dependencies/MelonStartScreen/UI/UI_Style.cs b/Dependencies/MelonStartScreen/UI/UI_Style.cs deleted file mode 100644 index 3ab8ac839..000000000 --- a/Dependencies/MelonStartScreen/UI/UI_Style.cs +++ /dev/null @@ -1,41 +0,0 @@ -using MelonUnityEngine; -using System; - -namespace MelonLoader.MelonStartScreen.UI -{ - internal class UI_Style - { - internal static Objects.UI_Background Background; - internal static Objects.UI_Image LogoImage; - internal static Objects.UI_Image LoadingImage; - internal static Objects.UI_Text VersionText; - internal static Objects.UI_ProgressBar ProgressBar; - - internal static void Init() - { - Background = new Objects.UI_Background(UI_Theme.Instance.Background); - VersionText = new Objects.UI_Text(UI_Theme.Instance.VersionText); - ProgressBar = new Objects.UI_ProgressBar(UI_Theme.Instance.ProgressBar, UI_Theme.Instance.ProgressText); - - if (UI_Theme.Instance.LogoImage.ScanForCustomImage) - LogoImage = UI_Utils.LoadImage(UI_Theme.Instance.LogoImage, "Logo"); - if (LogoImage == null) - LogoImage = new Objects.UI_Image(UI_Theme.Instance.LogoImage, UI_Theme.Instance.GetLogoImage()); - - if (UI_Theme.Instance.LoadingImage.ScanForCustomImage) - LoadingImage = UI_Utils.LoadImage(UI_Theme.Instance.LoadingImage, "Loading"); - if (LoadingImage == null) - LoadingImage = new Objects.UI_AnimatedImage(UI_Theme.Instance.LoadingImage, UI_Theme.Instance.GetLoadingImage()); - } - - - internal static void Render() - { - Background.Render(); - LogoImage.Render(); - LoadingImage.Render(); - ProgressBar.Render(); - VersionText.Render(); - } - } -} \ No newline at end of file diff --git a/Dependencies/MelonStartScreen/UI/UI_Theme.cs b/Dependencies/MelonStartScreen/UI/UI_Theme.cs deleted file mode 100644 index d70733222..000000000 --- a/Dependencies/MelonStartScreen/UI/UI_Theme.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System.IO; -using MelonUnityEngine; -using Tomlet; -using Tomlet.Attributes; -using Tomlet.Models; -using MelonLoader.MelonStartScreen.UI; -using System; - -namespace MelonLoader.MelonStartScreen -{ - internal abstract class UI_Theme - { - internal static UI_Theme Instance; - - private static string[] includedThemeIDs = - { - "Default", - "Random", - "Pumpkin" - }; - internal static bool IsIncludedThemeID() { foreach (var id in includedThemeIDs) if (id.Equals(ThemeID)) return true; return false; } - internal static string ThemeID = includedThemeIDs[0]; - - internal static string FilePath; - internal static string ThemePath; - internal static cGeneral General; - internal static bool IsLemon; - internal static bool IsPumpkin; - - internal cBackground Background; - internal ImageSettings LogoImage; - internal ImageSettings LoadingImage; - internal TextSettings ProgressText; - internal cProgressBar ProgressBar; - internal VersionTextSettings VersionText; - - internal void Defaults() - { - if (Background == null) - Background = CreateCat(nameof(Background), true); - if (LogoImage == null) - LogoImage = CreateCat(nameof(LogoImage), true); - if (LoadingImage == null) - LoadingImage = CreateCat(nameof(LoadingImage), true); - if (ProgressText == null) - ProgressText = CreateCat(nameof(ProgressText), true); - if (ProgressBar == null) - ProgressBar = CreateCat(nameof(ProgressBar), true); - if (VersionText == null) - VersionText = CreateCat(nameof(VersionText), true); - } - - internal static void Load() - { - TomletMain.RegisterMapper(WriteColor, ReadColor); - - FilePath = Path.Combine(Core.FolderPath, "Config.cfg"); - General = CreateCat(FilePath, nameof(General)); - - if (!string.IsNullOrEmpty(General.Theme)) - ThemeID = General.Theme - .Replace("\\", "") - .Replace("/", ""); - - if (ThemeID == "Halloween") - General.Theme = ThemeID = includedThemeIDs[0]; - - bool isIncludedID = IsIncludedThemeID(); - - if (ThemeID.Equals(includedThemeIDs[1])) - ThemePath = UI_Utils.RandomFolder(Core.ThemesFolderPath); - else - ThemePath = Path.Combine(Core.ThemesFolderPath, ThemeID); - - if (!isIncludedID && !Directory.Exists(ThemePath)) - { - Core.Logger.Error($"Failed to find Start Screen Theme: {ThemeID}"); - - isIncludedID = true; - General.Theme = ThemeID = includedThemeIDs[0]; - ThemePath = Path.Combine(Core.ThemesFolderPath, ThemeID); - } - - if (isIncludedID && !ThemeID.Equals(includedThemeIDs[1])) - { - // Lemon - IsLemon = (MelonLaunchOptions.Console.Mode == MelonLaunchOptions.Console.DisplayMode.LEMON); - if (!IsLemon) - { - // Pumpkin - IsPumpkin = ThemeID.Equals(includedThemeIDs[2]); - var nowTime = DateTime.Now; - if ((nowTime.Month == 10) - && (nowTime.Day == 31)) - IsPumpkin = true; - } - - ThemeID = "Default"; - ThemePath = Path.Combine(Core.ThemesFolderPath, ThemeID); - } - - if (!Directory.Exists(ThemePath)) - Directory.CreateDirectory(ThemePath); - - if (isIncludedID) - MelonPreferences.SaveCategory(nameof(General), false); - - string themeName = ( - IsPumpkin ? "Pumpkin" - : (IsLemon ? "Lemon" : ThemeID)); - - Core.Logger.Msg($"Using Start Screen Theme: \"{themeName}\""); - - Instance = - IsPumpkin ? new UI.Themes.UI_Theme_Pumpkin() - : (IsLemon ? new UI.Themes.UI_Theme_Lemon() - : new UI.Themes.UI_Theme_Default()); - } - - internal abstract byte[] GetLogoImage(); - internal abstract byte[] GetLoadingImage(); - - internal static T CreateCat(string name, bool shouldRemoveOld = false) where T : new() => CreateCat(Path.Combine(ThemePath, $"{name}.cfg"), name, shouldRemoveOld); - internal static T CreateCat(string filePath, string name, bool shouldRemoveOld = false) where T : new() - { - if (shouldRemoveOld) - MelonPreferences.RemoveCategoryFromFile(FilePath, name); - Preferences.MelonPreferences_ReflectiveCategory cat = MelonPreferences.CreateCategory(name, name); - cat.SetFilePath(filePath, true, false); - cat.SaveToFile(false); - cat.DestroyFileWatcher(); - return cat.GetValue(); - } - - internal class cGeneral - { - [TomlPrecedingComment("Toggles the Entire Start Screen ( true | false )")] - internal bool Enabled = true; - [TomlPrecedingComment("Current Theme of the Start Screen")] - internal string Theme = "Default"; - } - - internal class cBackground : ImageSettings - { - [TomlPrecedingComment("Solid RGBA Color of the Background")] - internal Color SolidColor = new Color(0.08f, 0.09f, 0.10f); - [TomlPrecedingComment("If it should stretch the Background to the Full Window Size")] - internal bool StretchToScreen = true; - } - - internal class ProgressTextSettings : TextSettings - { - public ProgressTextSettings() - { - Position.Item2 = 56; - Anchor = UI_Anchor.MiddleCenter; - ScreenAnchor = UI_Anchor.MiddleCenter; - } - } - - internal class cProgressBar : ElementSettings - { - public cProgressBar() => Defaults(); - public void Defaults() - { - Position.Item2 = 56; - Size.Item1 = 540; - Size.Item2 = 36; - - Anchor = UI_Anchor.MiddleCenter; - ScreenAnchor = UI_Anchor.MiddleCenter; - } - - [TomlPrecedingComment("Inner RGBA Color of the Progress Bar")] - internal Color InnerColor = new Color(1.00f, 0.23f, 0.42f); - [TomlPrecedingComment("Outer RGBA Color of the Progress Bar")] - internal Color OuterColor = new Color(0.47f, 0.97f, 0.39f); - } - - internal class VersionTextSettings : TextSettings - { - internal bool Is_ALPHA_PreRelease = false; - public VersionTextSettings() => Defaults(); - public void Defaults() - { - if (Text == null) - Text = $" v {(Is_ALPHA_PreRelease ? "ALPHA Pre-Release" : "Open-Beta")}"; - TextSize = 24; - Anchor = UI_Anchor.MiddleCenter; - ScreenAnchor = UI_Anchor.MiddleCenter; - Position.Item2 = 16; - } - } - - internal class LogoImageSettings : ImageSettings - { - public LogoImageSettings() - { - Size.Item1 = 262; - Size.Item2 = 212; - Position.Item2 = -(Size.Item2 / 2); - - Anchor = UI_Anchor.MiddleCenter; - ScreenAnchor = UI_Anchor.MiddleCenter; - } - } - - internal class LoadingImageSettings : ImageSettings - { - public LoadingImageSettings() - { - Position.Item2 = 35; - Size.Item1 = 200; - Size.Item2 = 132; - - Anchor = UI_Anchor.LowerRight; - ScreenAnchor = UI_Anchor.LowerRight; - } - } - - internal class ElementSettings - { - [TomlPrecedingComment("Toggles the Element ( true | false )")] - internal bool Enabled = true; - - [TomlPrecedingComment("Position of the Element")] - internal LemonTuple Position = new LemonTuple(); - [TomlPrecedingComment("Size of the Element")] - internal LemonTuple Size = new LemonTuple(); - - [TomlPrecedingComment("Anchor of the Text relative to Itself ( \"None\" | \"UpperLeft\" | \"UpperCenter\" | \"UpperRight\" | \"MiddleLeft\" | \"MiddleCenter\" | \"MiddleRight\" | \"LowerLeft\" | \"LowerCenter\" | \"LowerRight\" )")] - internal UI_Anchor Anchor = UI_Anchor.UpperLeft; - [TomlPrecedingComment("Anchor of the Text relative to the Screen ( \"None\" | \"UpperLeft\" | \"UpperCenter\" | \"UpperRight\" | \"MiddleLeft\" | \"MiddleCenter\" | \"MiddleRight\" | \"LowerLeft\" | \"LowerCenter\" | \"LowerRight\" )")] - internal UI_Anchor ScreenAnchor = UI_Anchor.UpperLeft; - } - - internal class TextSettings : ElementSettings - { - [TomlPrecedingComment("UnityEngine.FontStyle of the Text ( \"Normal\" | \"Bold\" | \"Italic\" | \"BoldAndItalic\" )")] - internal FontStyle Style = FontStyle.Bold; - [TomlPrecedingComment("Is this Rich Text ( true | false )")] - internal bool RichText = true; - [TomlPrecedingComment("Size of the Text")] - internal int TextSize = 16; - [TomlPrecedingComment("Scale of the Text")] - internal float Scale = 1f; - [TomlPrecedingComment("Font of the Text")] - internal string Font = "Arial"; - [TomlPrecedingComment("Scale of Line Spacing of the Text")] - internal float LineSpacing = 1f; - [TomlPrecedingComment("RGBA Color of the Text")] - internal Color TextColor = new Color(1, 1, 1); - [TomlPrecedingComment("Text to be Displayed")] - internal string Text; - } - - internal class ImageSettings : ElementSettings - { - [TomlPrecedingComment("If should Load Custom Image ( true | false )")] - internal bool ScanForCustomImage = true; - - [TomlPrecedingComment("UnityEngine.FilterMode of the Image ( \"Point\" | \"Bilinear\" | \"Trilinear\" )")] - internal FilterMode Filter = FilterMode.Bilinear; - - [TomlPrecedingComment("If the Image should attempt to Maintain it's Aspect Ratio")] - internal bool MaintainAspectRatio = false; - } - - private static Color ReadColor(TomlValue value) - { - float[] floats = MelonPreferences.Mapper.ReadArray(value); - if (floats == null || floats.Length != 4) - return default; - return new Color(floats[0] / 255f, floats[1] / 255f, floats[2] / 255f, floats[3] / 255f); - } - - private static TomlValue WriteColor(Color value) - { - float[] floats = new[] { value.r * 255, value.g * 255, value.b * 255, value.a * 255 }; - return MelonPreferences.Mapper.WriteArray(floats); - } - } -} diff --git a/Dependencies/MelonStartScreen/UI/UI_Utils.cs b/Dependencies/MelonStartScreen/UI/UI_Utils.cs deleted file mode 100644 index 40943a57f..000000000 --- a/Dependencies/MelonStartScreen/UI/UI_Utils.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System.IO; -using System.Linq; -using MelonUnityEngine; - -namespace MelonLoader.MelonStartScreen.UI -{ - internal static class UI_Utils - { - internal static Texture2D CreateColorTexture(Color color) - { - Texture2D texture = new Texture2D(2, 2); - texture.SetPixels(new Color[] { color, color, color, color }); - texture.Apply(); - return texture; - } - - internal static Objects.UI_Image LoadImage(UI_Theme.ImageSettings imageSettings, string filename) - { - string filepath = ScanForFile(UI_Theme.ThemePath, filename, new string[] { ".gif", ".png", ".jpg", ".jpeg" }); - if (string.IsNullOrEmpty(filepath)) - return null; - - string fileext = Path.GetExtension(filepath).ToLowerInvariant(); - - if (fileext.Equals(".gif")) - return new Objects.UI_AnimatedImage(imageSettings, filepath); - - if (fileext.Equals(".png") - || fileext.Equals(".jpg") - || fileext.Equals(".jpeg")) - return new Objects.UI_Image(imageSettings, filepath); - - return null; - } - - internal static string ScanForFile(string folderPath, string filename, string[] fileExts) - { - string[] files = Directory.GetFiles(folderPath); - if (files.Length <= 0) - return null; - - string filename_lower = filename.ToLowerInvariant(); - return files.FirstOrDefault((string filepath) => - { - string currentfilename = Path.GetFileNameWithoutExtension(filepath).ToLowerInvariant(); - if (!currentfilename.Equals(filename_lower)) - return false; - - string fileext = Path.GetExtension(filepath).ToLowerInvariant(); - return fileExts.Contains(fileext); - }); - } - - internal static string RandomFolder(string folderPath) - { - string[] files = Directory.GetDirectories(folderPath); - if (files.Length <= 0) - return null; - return files[MelonUtils.RandomInt(0, files.Length)]; - } - - internal static void AnchorToScreen(UI_Anchor anchor, int x, int y, out int out_x, out int out_y) - { - int sw = Screen.width; - int sh = Screen.height - 35; - - switch (anchor) - { - // Upper - case UI_Anchor.UpperLeft: - y = sh - y; - goto default; - case UI_Anchor.UpperCenter: - x = (sw / 2) + x; - y = sh - y; - goto default; - case UI_Anchor.UpperRight: - x = sw - x; - y = sh - y; - goto default; - - // Middle - case UI_Anchor.MiddleLeft: - y = (sh / 2) - y; - goto default; - case UI_Anchor.MiddleCenter: - x = (sw / 2) + x; - y = (sh / 2) - y; - goto default; - case UI_Anchor.MiddleRight: - x = sw - x; - y = (sh / 2) - y; - goto default; - - // Lower - case UI_Anchor.LowerCenter: - x = (sw / 2) + x; - goto default; - - case UI_Anchor.LowerRight: - x = sw - x; - goto default; - - // End - case UI_Anchor.LowerLeft: - default: - out_x = x; - out_y = y; - break; - } - } - - internal static void AnchorToObject(UI_Anchor anchor, int x, int y, int width, int height, out int out_x, out int out_y) - { - switch (anchor) - { - // Upper - case UI_Anchor.UpperCenter: - x -= (width / 2); - goto default; - case UI_Anchor.UpperRight: - x -= width; - goto default; - - // Middle - case UI_Anchor.MiddleLeft: - y -= (height / 2); - goto default; - case UI_Anchor.MiddleCenter: - y -= (height / 2); - x -= (width / 2); - goto default; - case UI_Anchor.MiddleRight: - y -= (height / 2); - x -= width; - goto default; - - // Lower - case UI_Anchor.LowerLeft: - y -= height; - goto default; - case UI_Anchor.LowerCenter: - y -= height; - x -= (width / 2); - goto default; - case UI_Anchor.LowerRight: - y -= height; - x -= width; - goto default; - - // End - case UI_Anchor.UpperLeft: - default: - out_x = x; - out_y = y; - break; - } - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppException.cs b/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppException.cs deleted file mode 100644 index 49c1552f1..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppException.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Text; -#pragma warning disable 0649 - -namespace UnhollowerMini -{ - internal class Il2CppException : Exception - { - [ThreadStatic] private static byte[] ourMessageBytes; - - public static Func ParseMessageHook; - - public Il2CppException(IntPtr exception) : base(BuildMessage(exception)) { } - - private static unsafe string BuildMessage(IntPtr exception) - { - if (ParseMessageHook != null) return ParseMessageHook(exception); - if (ourMessageBytes == null) ourMessageBytes = new byte[65536]; - fixed (byte* message = ourMessageBytes) - UnityInternals.format_exception(exception, message, ourMessageBytes.Length); - string builtMessage = Encoding.UTF8.GetString(ourMessageBytes, 0, Array.IndexOf(ourMessageBytes, (byte)0)); - fixed (byte* message = ourMessageBytes) - UnityInternals.format_stack_trace(exception, message, ourMessageBytes.Length); - builtMessage += - "\n" + Encoding.UTF8.GetString(ourMessageBytes, 0, Array.IndexOf(ourMessageBytes, (byte)0)); - return builtMessage; - } - - public static void RaiseExceptionIfNecessary(IntPtr returnedException) - { - if (returnedException == IntPtr.Zero) return; - throw new Il2CppException(returnedException); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Byte.cs b/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Byte.cs deleted file mode 100644 index fd8c9d495..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Byte.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace Il2CppSystem -{ - [StructLayout(LayoutKind.Explicit)] - internal class Byte - { - [FieldOffset(0)] - public byte m_value; - - static Byte() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("mscorlib.dll", "System", "Byte"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Int32.cs b/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Int32.cs deleted file mode 100644 index c7e2df520..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Int32.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace Il2CppSystem -{ - [StructLayout(LayoutKind.Explicit)] - internal class Int32 - { - [FieldOffset(0)] - public int m_value; - - static Int32() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("mscorlib.dll", "System", "Int32"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Type.cs b/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Type.cs deleted file mode 100644 index 307428ab3..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/Il2CppSystem/Type.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MelonLoader; -using System; -using UnhollowerMini; - -namespace Il2CppSystem -{ - internal class Type : InternalObjectBase - { - private static readonly IntPtr m_internal_from_handle; - - static Type() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("mscorlib.dll", "System", "Type"); - - m_internal_from_handle = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "internal_from_handle", "System.Type", "System.IntPtr"); - } - - public Type(IntPtr ptr) : base(ptr) { } - - public unsafe static Type internal_from_handle(IntPtr handle) - { - void** args = stackalloc void*[1]; - args[0] = &handle; - IntPtr returnedException = default; - IntPtr intPtr = UnityInternals.runtime_invoke(Type.m_internal_from_handle, IntPtr.Zero, (void**)args, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - return (intPtr != IntPtr.Zero) ? new Type(intPtr) : null; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/InternalClassPointerStore.cs b/Dependencies/MelonStartScreen/UnhollowerMini/InternalClassPointerStore.cs deleted file mode 100644 index 37e0b0615..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/InternalClassPointerStore.cs +++ /dev/null @@ -1,27 +0,0 @@ -using MelonLoader; -using System; -using System.Runtime.CompilerServices; -#pragma warning disable 0649 - -namespace UnhollowerMini -{ - internal static class InternalClassPointerStore - { - public static IntPtr NativeClassPtr; - public static Type CreatedTypeRedirect; - - static InternalClassPointerStore() - { - var targetType = typeof(T); - - RuntimeHelpers.RunClassConstructor(targetType.TypeHandle); - - if (targetType.IsPrimitive || targetType == typeof(string)) - { - MelonDebug.Msg("Running class constructor on Il2Cpp" + targetType.FullName); - RuntimeHelpers.RunClassConstructor(typeof(InternalClassPointerStore<>).Assembly.GetType("Il2Cpp" + targetType.FullName).TypeHandle); - MelonDebug.Msg("Done running class constructor"); - } - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/InternalObjectBase.cs b/Dependencies/MelonStartScreen/UnhollowerMini/InternalObjectBase.cs deleted file mode 100644 index 478df5a69..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/InternalObjectBase.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace UnhollowerMini -{ - internal class InternalObjectBase - { - public IntPtr Pointer - { - get - { - var handleTarget = UnityInternals.gchandle_get_target(myGcHandle); - if (handleTarget == IntPtr.Zero) throw new ObjectCollectedException("Object was garbage collected"); - return handleTarget; - } - } - - protected uint myGcHandle; - - protected InternalObjectBase() { } - - public InternalObjectBase(IntPtr pointer) - { - if (pointer == IntPtr.Zero) - throw new NullReferenceException(); - - myGcHandle = UnityInternals.gchandle_new(pointer, false); - } - - ~InternalObjectBase() - { - UnityInternals.gchandle_free(myGcHandle); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/InternalType.cs b/Dependencies/MelonStartScreen/UnhollowerMini/InternalType.cs deleted file mode 100644 index fa312cd03..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/InternalType.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MelonLoader; -using System; - -namespace UnhollowerMini -{ - internal static class InternalType - { - public static Il2CppSystem.Type TypeFromPointer(IntPtr classPointer, string typeName = "") - { - if (classPointer == IntPtr.Zero) throw new ArgumentException($"{typeName} does not have a corresponding internal class pointer"); - var il2CppType = UnityInternals.class_get_type(classPointer); - if (il2CppType == IntPtr.Zero) throw new ArgumentException($"{typeName} does not have a corresponding class type pointer"); - return Il2CppSystem.Type.internal_from_handle(il2CppType); - } - - public static Il2CppSystem.Type Of() - { - var classPointer = InternalClassPointerStore.NativeClassPtr; - return TypeFromPointer(classPointer, typeof(T).Name); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/ObjectCollectedException.cs b/Dependencies/MelonStartScreen/UnhollowerMini/ObjectCollectedException.cs deleted file mode 100644 index dd681b28b..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/ObjectCollectedException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace UnhollowerMini -{ - internal class ObjectCollectedException : Exception - { - public ObjectCollectedException(string message) : base(message) { } - } -} diff --git a/Dependencies/MelonStartScreen/UnhollowerMini/UnityInternals.cs b/Dependencies/MelonStartScreen/UnhollowerMini/UnityInternals.cs deleted file mode 100644 index 9265239fb..000000000 --- a/Dependencies/MelonStartScreen/UnhollowerMini/UnityInternals.cs +++ /dev/null @@ -1,696 +0,0 @@ -using MelonLoader; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; - -namespace UnhollowerMini -{ - internal static unsafe class UnityInternals - { - private delegate void delegate_gfunc_mono_assembly_foreach(IntPtr assembly, IntPtr user_data); - - private static readonly IntPtr domain; - private static readonly List assemblies = new List(); - - private static readonly uint monoClassOffset = 0; - - unsafe static UnityInternals() - { - if (MelonUtils.IsGameIl2Cpp()) - { - domain = il2cpp_domain_get(); - - uint assemblyCount = 0; - IntPtr* assemblyArray = il2cpp_domain_get_assemblies(domain, ref assemblyCount); - for (int i = 0; i < assemblyCount; ++i) - assemblies.Add(new InternalAssembly(il2cpp_assembly_get_image(*(assemblyArray + i)))); - } - else - { - domain = mono_domain_get(); - - MonoClass* testclass = (MonoClass*)Marshal.AllocHGlobal(sizeof(MonoClass)); - testclass->applyZeroes(); - testclass->nested_in_0x04 = (IntPtr)0x1234; - testclass->nested_in_0x08 = (IntPtr)0x5678; - testclass->nested_in_0x0C = (IntPtr)0x9012; - long returnedName = (long)mono_class_get_name((IntPtr)testclass); - MelonDebug.Msg($"returnedName {returnedName:X}"); - Marshal.FreeHGlobal((IntPtr)testclass); - if (returnedName == 0x1234) - monoClassOffset = 0; - else if (returnedName == 0x5678) - monoClassOffset = (uint)IntPtr.Size * 1; - else if (returnedName == 0x9012) - monoClassOffset = (uint)IntPtr.Size * 2; - else - throw new Exception("Failed to find MonoClass name offset"); - - MelonDebug.Msg("monoClassOffset? " + monoClassOffset); - } - } - - private class InternalAssembly - { - public IntPtr ptr; - public string name; - - public InternalAssembly(IntPtr ptr) - { - this.ptr = ptr; - if (MelonUtils.IsGameIl2Cpp()) - { - name = Marshal.PtrToStringAnsi(il2cpp_image_get_filename(this.ptr)); - } - else - { - name = Marshal.PtrToStringAnsi(mono_image_get_filename(this.ptr)); - } - } - } - - private class InternalClass - { - public IntPtr ptr; - public string name; - public string name_space; - - public InternalClass(IntPtr ptr) - { - this.ptr = ptr; - if (MelonUtils.IsGameIl2Cpp()) - { - name = Marshal.PtrToStringAnsi(il2cpp_class_get_name(ptr)); - name_space = Marshal.PtrToStringAnsi(il2cpp_class_get_namespace(ptr)); - } - else - { - throw new NotImplementedException(); - } - } - - public InternalClass(IntPtr ptr, string name, string name_space) - { - if (MelonUtils.IsGameIl2Cpp()) - { - throw new NotImplementedException(); - } - else - { - this.ptr = ptr; - this.name = name; - this.name_space = name_space; - } - } - } - - - internal static IntPtr GetClass(string assemblyname, string name_space, string classname) - { - MelonDebug.Msg($"GetClass {assemblyname} {name_space} {classname}"); - if (MelonUtils.IsGameIl2Cpp()) - { - InternalAssembly assembly = assemblies.FirstOrDefault(a => a.name == assemblyname); - if (assembly == null) - { - throw new Exception("Unable to find assembly " + assemblyname + " in il2cpp domain"); - } - - IntPtr clazz = il2cpp_class_from_name(assembly.ptr, name_space, classname); - if (clazz == IntPtr.Zero) - { - throw new Exception("Unable to find class " + name_space + "." + classname + " in assembly " + assemblyname); - } - - MelonDebug.Msg($" > 0x{(long)clazz:X}"); - return clazz; - } - else - { - string fullname = string.IsNullOrEmpty(name_space) ? "" : (name_space + ".") + classname; - - Assembly ass = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name + ".dll" == assemblyname); - if (ass == null) - { - throw new Exception("Unable to find assembly " + assemblyname + " in mono domain"); - } - - Type t = ass.GetType(fullname); - if (t == null) - { - throw new Exception("Unable to find class " + fullname + " in assembly " + assemblyname); - } - MelonDebug.Msg($" > 0x{(long)(*(IntPtr*)t.TypeHandle.Value):X}"); - - return *(IntPtr*)t.TypeHandle.Value; - } - } - - public static IntPtr GetField(IntPtr clazz, string fieldName) - { - MelonDebug.Msg($"GetField {fieldName}"); - if (clazz == IntPtr.Zero) - return IntPtr.Zero; - - var field = MelonUtils.IsGameIl2Cpp() ? il2cpp_class_get_field_from_name(clazz, fieldName) : mono_class_get_field_from_name(clazz, fieldName); - if (field == IntPtr.Zero) - throw new Exception($"Field {fieldName} was not found on class {Marshal.PtrToStringAnsi(MelonUtils.IsGameIl2Cpp() ? il2cpp_class_get_name(clazz) : mono_class_get_name(clazz))}"); - MelonDebug.Msg($" > 0x{(long)field:X}"); - return field; - } - - internal static IntPtr GetMethod(IntPtr clazz, string name, string returntype, params string[] parameters) - { - MelonDebug.Msg($"GetMethod {returntype} {name}({string.Join(", ", parameters)})"); - if (MelonUtils.IsGameIl2Cpp()) - { - IntPtr iter = IntPtr.Zero; - IntPtr element; - while ((element = il2cpp_class_get_methods(clazz, ref iter)) != IntPtr.Zero) - { - if (Marshal.PtrToStringAnsi(il2cpp_method_get_name(element)) != name) - continue; - - if (Marshal.PtrToStringAnsi(il2cpp_type_get_name(il2cpp_method_get_return_type(element))) != returntype) - continue; - - if (parameters.Length != il2cpp_method_get_param_count(element)) - continue; - - bool hasValidParameters = true; - for (uint i = 0; i < parameters.Length; ++i) - { - if (Marshal.PtrToStringAnsi(il2cpp_type_get_name(il2cpp_method_get_param(element, i))) != parameters[i]) - { - hasValidParameters = false; - break; - } - } - - if (hasValidParameters) - { - MelonDebug.Msg($" > 0x{(long)element:X}"); - return element; - } - } - } - else - { - - IntPtr iter = IntPtr.Zero; - IntPtr element; - while ((element = mono_class_get_methods(clazz, ref iter)) != IntPtr.Zero) - { - if (Marshal.PtrToStringAnsi(mono_method_get_name(element)) != name) - continue; - - IntPtr sig = mono_method_get_signature(element, IntPtr.Zero, 0); - if (Marshal.PtrToStringAnsi(mono_type_get_name(mono_signature_get_return_type(sig))) != returntype) - continue; - if (parameters.Length != mono_signature_get_param_count(sig)) - continue; - - bool hasValidParameters = true; - IntPtr iter2 = IntPtr.Zero; - IntPtr param; - int i = 0; - while ((param = mono_signature_get_params(sig, ref iter2)) != IntPtr.Zero) - { - if (Marshal.PtrToStringAnsi(mono_type_get_name(param)) != parameters[i]) - { - hasValidParameters = false; - break; - } - ++i; - } - - if (hasValidParameters) - { - MelonDebug.Msg($" > 0x{(long)element:X}"); - return element; - } - } - } - //MelonLogger.Error($"Unable to find method {returntype} {name}({string.Join(", ", parameters)})"); - //return IntPtr.Zero; - throw new Exception($"Unable to find method {returntype} {name}({string.Join(", ", parameters)})"); - } - - public static IntPtr ObjectBaseToPtr(InternalObjectBase obj) - { - return obj?.Pointer ?? IntPtr.Zero; - } - - public static IntPtr ObjectBaseToPtrNotNull(InternalObjectBase obj) - { - return obj?.Pointer ?? throw new NullReferenceException(); - } - - public static IntPtr ManagedStringToInternal(string str) - { - if (str == null) return IntPtr.Zero; - - fixed (char* chars = str) - return MelonUtils.IsGameIl2Cpp() ? il2cpp_string_new_utf16(chars, str.Length) : mono_string_new_utf16(domain, chars, str.Length); - } - - public static IntPtr ResolveICall(string signature) - { - MelonDebug.Msg("Resolving ICall " + signature); - IntPtr icallPtr; - if (MelonUtils.IsGameIl2Cpp()) - icallPtr = il2cpp_resolve_icall(signature); - else - { - // We generate a fake MonoMethod + MonoMethodSignature + MonoClass struct to exploit the lookup code and force resolve our icall without the class/method being registered - // (Slaynash: Yes this is illegal) - MonoMethod* monoMethod = IcallToFakeMonoMethod(signature); - icallPtr = mono_lookup_internal_call((IntPtr)monoMethod); - DestroyFakeMonoMethod(monoMethod); - } - - if (icallPtr == IntPtr.Zero) - { - //MelonLogger.Error($"ICall {signature} not resolved"); - //return IntPtr.Zero; - throw new Exception($"ICall {signature} not resolved"); - } - MelonDebug.Msg($" > 0x{(long)icallPtr:X}"); - - return icallPtr; - } - - public static T ResolveICall(string signature) where T : Delegate - { - IntPtr icallPtr = ResolveICall(signature); - return icallPtr == IntPtr.Zero ? null : (T)Marshal.GetDelegateForFunctionPointer(icallPtr, typeof(T)); - } - - private static unsafe MonoMethod* IcallToFakeMonoMethod(string icallName) - { - string[] typeAndMethod = icallName.Split(new[] { "::" }, StringSplitOptions.None); - int parenthesisIndex = typeAndMethod[1].IndexOf('('); - if (parenthesisIndex >= 0) - typeAndMethod[1] = typeAndMethod[1].Substring(0, parenthesisIndex); - // We add a padding to the end of each allocated memory since our structs are supposed to be bigger than the one we have here - MonoMethod* monoMethod = (MonoMethod*)Marshal.AllocHGlobal(sizeof(MonoMethod) + 0x100); - monoMethod->applyZeroes(); - monoMethod->klass = (MonoClass*)Marshal.AllocHGlobal(sizeof(MonoClass) + 0x100); - monoMethod->klass->applyZeroes(); - monoMethod->name = (byte*)Marshal.StringToHGlobalAnsi(typeAndMethod[1]); - int lastDotIndex = typeAndMethod[0].LastIndexOf('.'); - if (lastDotIndex < 0) - { - *(IntPtr*)((long)&monoMethod->klass->nested_in_0x08 + monoClassOffset) = Marshal.StringToHGlobalAnsi(""); - *(IntPtr*)((long)&monoMethod->klass->nested_in_0x04 + monoClassOffset) = Marshal.StringToHGlobalAnsi(typeAndMethod[0]); - } - else - { - string name_space = typeAndMethod[0].Substring(0, lastDotIndex); - string name = typeAndMethod[0].Substring(lastDotIndex + 1); - *(IntPtr*)((long)&monoMethod->klass->nested_in_0x08 + monoClassOffset) = Marshal.StringToHGlobalAnsi(name_space); - *(IntPtr*)((long)&monoMethod->klass->nested_in_0x04 + monoClassOffset) = Marshal.StringToHGlobalAnsi(name); - } - - MonoMethodSignature* monoMethodSignature = (MonoMethodSignature*)Marshal.AllocHGlobal(sizeof(MonoMethodSignature)); - monoMethodSignature->ApplyZeroes(); - monoMethod->signature = monoMethodSignature; - - return monoMethod; - } - - private static unsafe void DestroyFakeMonoMethod(MonoMethod* monoMethod) - { - Marshal.FreeHGlobal((IntPtr)monoMethod->signature); - Marshal.FreeHGlobal(*(IntPtr*)((long)&monoMethod->klass->nested_in_0x04 + monoClassOffset)); - Marshal.FreeHGlobal(*(IntPtr*)((long)&monoMethod->klass->nested_in_0x08 + monoClassOffset)); - Marshal.FreeHGlobal((IntPtr)monoMethod->klass); - Marshal.FreeHGlobal((IntPtr)monoMethod->name); - Marshal.FreeHGlobal((IntPtr)monoMethod); - } - - [StructLayout(LayoutKind.Sequential)] - private struct MonoMethod - { - public ushort flags; /* method flags */ - public ushort iflags; /* method implementation flags */ - public uint token; - public MonoClass* klass; /* To what class does this method belong */ - public MonoMethodSignature* signature; - /* name is useful mostly for debugging */ - public byte* name; - - public IntPtr method_pointer; - public IntPtr invoke_pointer; - - /* this is used by the inlining algorithm */ - public ushort bitfield; - public int slot; - - internal void applyZeroes() - { - flags = 0; - iflags = 0; - token = 0; - klass = (MonoClass*)0; - signature = (MonoMethodSignature*)0; - name = (byte*)0; - method_pointer = IntPtr.Zero; - invoke_pointer = IntPtr.Zero; - bitfield = 0; - slot = 0; - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct MonoMethodSignature - { - public IntPtr ret; - public ushort param_cout; - // ... - - internal void ApplyZeroes() - { - ret = (IntPtr)0; - param_cout = 0; - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct MonoClass - { - /* element class for arrays and enum basetype for enums */ - public MonoClass* element_class; - /* used for subtype checks */ - public MonoClass* cast_class; - - /* for fast subtype checks */ - public MonoClass** supertypes; - public ushort idepth; - - /* array dimension */ - public byte rank; // 0x0E - - /* One of the values from MonoTypeKind */ - public byte class_kind; - - public int instance_size; // 0x10 - - public uint bitfield1; // 0x14 - - public byte min_align; // 0x18 - - public uint bitfield2; // 0x1C - - byte exception_type; - - public MonoClass* parent; // 0x24 - public MonoClass* nested_in; // 0x28 // Should always be null - - //public IntPtr cattrs; // 0x2C - - // - // Starting here (unity version from 2014+), fields will be offset by IntPtr.Size - // - - public IntPtr nested_in_0x04; // 0x2C - 0x30 - public IntPtr nested_in_0x08; // 0x30 - 0x34 - public IntPtr nested_in_0x0C; // 0x34 - 0x38 - public IntPtr nested_in_0x10; // 0x38 - 0x3C - - // ... - - internal void applyZeroes() - { - element_class = (MonoClass*)0; - cast_class = (MonoClass*)0; - supertypes = (MonoClass**)0; - idepth = 0; - rank = 0; - class_kind = 0; - instance_size = 0; - bitfield1 = 0; - min_align = 0; - bitfield2 = 0; - exception_type = 0; - parent = (MonoClass*)0; - nested_in = (MonoClass*)0; - nested_in_0x04 = (IntPtr)0; - nested_in_0x08 = (IntPtr)0; - nested_in_0x0C = (IntPtr)0; - nested_in_0x10 = (IntPtr)0; - } - } - - private struct MonoType - { - public IntPtr data; - public short attrs; - public byte type; - public byte bitflags; - - internal void applyZeroes() - { - data = (IntPtr)0; - attrs = 0; - type = 0; - bitflags = 0; - } - } - - public static IntPtr class_get_type(IntPtr klass) - { - return MelonUtils.IsGameIl2Cpp() ? il2cpp_class_get_type(klass) : mono_class_get_type(klass); - } - - public static void runtime_class_init(IntPtr klass) - { - if (klass == IntPtr.Zero) - throw new ArgumentException("Class to init is null"); - - if (MelonUtils.IsGameIl2Cpp()) il2cpp_runtime_class_init(klass); else mono_runtime_class_init(klass); - } - - public static IntPtr runtime_invoke(IntPtr method, IntPtr obj, void** param, ref IntPtr exc) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_runtime_invoke(method, obj, param, ref exc) : mono_runtime_invoke(method, obj, param, ref exc); - - public static IntPtr array_new(IntPtr elementTypeInfo, ulong length) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_array_new(elementTypeInfo, length) : mono_array_new(domain, elementTypeInfo, length); - - public static uint array_length(IntPtr array) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_array_length(array) : *(uint*)((long)array + IntPtr.Size * 3); - - public static uint field_get_offset(IntPtr field) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_field_get_offset(field) : mono_field_get_offset(field); - - public static IntPtr object_unbox(IntPtr obj) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_object_unbox(obj) : mono_object_unbox(obj); - - public static IntPtr object_new(IntPtr klass) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_object_new(klass) : mono_object_new(domain, klass); - - public static int class_value_size(IntPtr klass, ref uint align) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_class_value_size(klass, ref align) : mono_class_value_size(klass, ref align); - - public static uint gchandle_new(IntPtr obj, bool pinned) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_gchandle_new(obj, pinned) : mono_gchandle_new(obj, pinned ? 1 : 0); - public static void gchandle_free(uint gchandle) - { if (MelonUtils.IsGameIl2Cpp()) il2cpp_gchandle_free(gchandle); else mono_gchandle_free(gchandle); } - public static IntPtr gchandle_get_target(uint gchandle) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_gchandle_get_target(gchandle) : mono_gchandle_get_target(gchandle); - - public static IntPtr value_box(IntPtr klass, IntPtr val) => - MelonUtils.IsGameIl2Cpp() ? il2cpp_value_box(klass, val) : mono_value_box(domain, klass, val); - - public static void format_exception(IntPtr ex, void* message, int message_size) - { - if (MelonUtils.IsGameIl2Cpp()) - il2cpp_format_exception(ex, message, message_size); - // TODO Mono mono_format_exception - } - - public static void format_stack_trace(IntPtr ex, void* output, int output_size) - { - if (MelonUtils.IsGameIl2Cpp()) - il2cpp_format_stack_trace(ex, output, output_size); - // TODO mono_format_stack_trace - } - - - // Mono Functions - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_domain_get(); - - /* - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr* mono_domain_get_assemblies(IntPtr domain, ref uint size); - */ - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void mono_assembly_foreach(delegate_gfunc_mono_assembly_foreach func, IntPtr user_data); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_assembly_get_image(IntPtr assembly); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_image_get_filename(IntPtr image); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint mono_image_get_class_count(IntPtr image); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_image_get_class(IntPtr image, uint index); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_class_get_name(IntPtr klass); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_class_get_namespace(IntPtr klass); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_lookup_internal_call(IntPtr method); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr mono_class_get_type(IntPtr klass); - - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern unsafe IntPtr mono_runtime_invoke(IntPtr method, IntPtr obj, void** param, ref IntPtr exc); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void mono_runtime_class_init(IntPtr klass); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_array_new(IntPtr domain, IntPtr eclass, ulong n); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint mono_field_get_offset(IntPtr field); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_object_unbox(IntPtr obj); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_object_new(IntPtr domain, IntPtr klass); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern int mono_class_value_size(IntPtr klass, ref uint align); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint mono_gchandle_new(IntPtr obj, int pinned); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void mono_gchandle_free(uint gchandle); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_gchandle_get_target(uint gchandle); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_class_get_field_from_name(IntPtr klass, [MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_value_box(IntPtr domain, IntPtr klass, IntPtr data); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_class_get_methods(IntPtr klass, ref IntPtr iter); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr mono_method_get_name(IntPtr method); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_type_get_name(IntPtr type); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_image_get_table_info(IntPtr image, int table_id); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern int mono_table_info_get_rows(IntPtr table); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void mono_metadata_decode_row(IntPtr t, int idx, uint[] res, int res_size); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_metadata_string_heap(IntPtr meta, uint index); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_class_from_name(IntPtr image, string name_space, string name); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_domain_try_type_resolve(IntPtr domain, string name, IntPtr typebuilder_raw); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_method_get_signature(IntPtr method, IntPtr image, uint token); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_signature_get_return_type(IntPtr sig); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint mono_signature_get_param_count(IntPtr sig); - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_signature_get_params(IntPtr sig, ref IntPtr iter); - - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr mono_string_new_utf16(IntPtr domain, char* text, int len); - - - - - // IL2CPP Functions - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_domain_get(); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint il2cpp_array_length(IntPtr array); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_array_new(IntPtr elementTypeInfo, ulong length); - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_assembly_get_image(IntPtr assembly); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_class_get_field_from_name(IntPtr klass, [MarshalAs(UnmanagedType.LPStr)] string name); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_class_get_methods(IntPtr klass, ref IntPtr iter); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_class_get_name(IntPtr klass); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_class_get_namespace(IntPtr klass); - - - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_class_get_type(IntPtr klass); - - - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern int il2cpp_class_value_size(IntPtr klass, ref uint align); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr* il2cpp_domain_get_assemblies(IntPtr domain, ref uint size); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void il2cpp_format_exception(IntPtr ex, void* message, int message_size); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void il2cpp_format_stack_trace(IntPtr ex, void* output, int output_size); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint il2cpp_field_get_offset(IntPtr field); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint il2cpp_gchandle_new(IntPtr obj, bool pinned); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_gchandle_get_target(uint gchandle); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void il2cpp_gchandle_free(uint gchandle); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_method_get_return_type(IntPtr method); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern uint il2cpp_method_get_param_count(IntPtr method); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_method_get_param(IntPtr method, uint index); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_method_get_name(IntPtr method); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_object_new(IntPtr klass); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_object_unbox(IntPtr obj); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_value_box(IntPtr klass, IntPtr data); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern unsafe IntPtr il2cpp_runtime_invoke(IntPtr method, IntPtr obj, void** param, ref IntPtr exc); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern void il2cpp_runtime_class_init(IntPtr klass); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_string_new_utf16(char* text, int len); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_type_get_name(IntPtr type); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_image_get_filename(IntPtr image); - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern IntPtr il2cpp_class_from_name(IntPtr image, string namespaze, string name); - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color.cs deleted file mode 100644 index f390cb358..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Explicit)] - internal struct Color - { - private static readonly IntPtr m_ToString; - - [FieldOffset(0)] - public float r; - [FieldOffset(4)] - public float g; - [FieldOffset(8)] - public float b; - [FieldOffset(12)] - public float a; - - static Color() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Color"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - m_ToString = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "ToString", "System.String"); - } - - public Color(float r, float g, float b, float a = 1f) - { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color32.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color32.cs deleted file mode 100644 index 39bc045c9..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Color32.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Explicit)] - internal struct Color32 - { - [FieldOffset(0)] - public byte r; - [FieldOffset(1)] - public byte g; - [FieldOffset(2)] - public byte b; - [FieldOffset(3)] - public byte a; - - [FieldOffset(0)] - public int rgba; - - static Color32() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Color32"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - } - - public Color32(byte r, byte g, byte b, byte a) - { - this.rgba = 0; - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } - - public static implicit operator Color(Color32 c) - { - return new Color(c.r / 255f, c.g / 255f, c.b / 255f, c.a / 255f); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/FilterMode.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/FilterMode.cs deleted file mode 100644 index 05122e2f4..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/FilterMode.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MelonUnityEngine -{ - internal enum FilterMode - { - Point, - Bilinear, - Trilinear - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/GL.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/GL.cs deleted file mode 100644 index 045bc4059..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/GL.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal sealed class GL - { - private delegate bool d_get_sRGBWrite(); - private static readonly d_get_sRGBWrite m_get_sRGBWrite; - - unsafe static GL() - { - m_get_sRGBWrite = UnityInternals.ResolveICall("UnityEngine.GL::get_sRGBWrite"); - } - - public unsafe static bool sRGBWrite - { - get => m_get_sRGBWrite(); - // set - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Graphics.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Graphics.cs deleted file mode 100644 index a01bd0ac3..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Graphics.cs +++ /dev/null @@ -1,117 +0,0 @@ -using MelonLoader; -using MelonLoader.MelonStartScreen.NativeUtils; -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class Graphics : InternalObjectBase - { - private delegate IntPtr Internal_DrawMeshNow1_InjectedDelegate(IntPtr mesh, int subsetIndex, ref Vector3 position, ref Quaternion rotation); - private delegate void Internal_DrawTextureDelegate2017(ref Internal_DrawTextureArguments_2017 args); - private delegate void Internal_DrawTextureDelegate2018(ref Internal_DrawTextureArguments_2018 args); - private delegate void Internal_DrawTextureDelegate2019(ref Internal_DrawTextureArguments_2019 args); - private delegate void Internal_DrawTextureDelegate2020(ref Internal_DrawTextureArguments_2020 args); - - private static readonly Internal_DrawTextureDelegate2017 fd_Internal_DrawTexture2017; - private static readonly Internal_DrawTextureDelegate2018 fd_Internal_DrawTexture2018; - private static readonly Internal_DrawTextureDelegate2019 fd_Internal_DrawTexture2019; - private static readonly Internal_DrawTextureDelegate2020 fd_Internal_DrawTexture2020; - private static readonly Internal_DrawMeshNow1_InjectedDelegate fd_Internal_DrawMeshNow1_Injected; - - private static readonly int m_DrawTexture_Internal_struct = -1; - - unsafe static Graphics() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Graphics"); - fd_Internal_DrawTexture2017 = UnityInternals.ResolveICall("UnityEngine.Graphics::Internal_DrawTexture"); - fd_Internal_DrawTexture2018 = UnityInternals.ResolveICall("UnityEngine.Graphics::Internal_DrawTexture"); - fd_Internal_DrawTexture2019 = UnityInternals.ResolveICall("UnityEngine.Graphics::Internal_DrawTexture"); - fd_Internal_DrawTexture2020 = UnityInternals.ResolveICall("UnityEngine.Graphics::Internal_DrawTexture"); - - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2018.2.0", "2019.1.0" })) - fd_Internal_DrawMeshNow1_Injected = UnityInternals.ResolveICall("UnityEngine.Graphics::Internal_DrawMeshNow1_Injected"); - else - fd_Internal_DrawMeshNow1_Injected = UnityInternals.ResolveICall("UnityEngine.Graphics::INTERNAL_CALL_Internal_DrawMeshNow1"); - - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2019.3.0", "2020.1.0" })) - m_DrawTexture_Internal_struct = 3; - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2018.2.0", "2019.1.0" })) - m_DrawTexture_Internal_struct = 2; - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2017.3.0", "2018.1.0" })) - m_DrawTexture_Internal_struct = 1; - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2017.2.0" })) - m_DrawTexture_Internal_struct = 0; - } - - public Graphics(IntPtr ptr) : base(ptr) { } - - public unsafe static void DrawTexture(Rect screenRect, Texture2D texture) - { - if ((texture == null) || (texture.Pointer == IntPtr.Zero)) - return; - - if (m_DrawTexture_Internal_struct == 0) - { - Internal_DrawTextureArguments_2017 args = default; - args.screenRect = screenRect; - args.sourceRect = new Rect(0, 0, 1, 1); - args.color = new Color32(128, 128, 128, 128); - args.texture = UnityInternals.ObjectBaseToPtrNotNull(texture); - fd_Internal_DrawTexture2017(ref args); - } - else if (m_DrawTexture_Internal_struct == 1) - { - Internal_DrawTextureArguments_2018 args = default; - args.screenRect = screenRect; - args.sourceRect = new Rect(0, 0, 1, 1); - args.color = new Color32(128, 128, 128, 128); - args.texture = UnityInternals.ObjectBaseToPtrNotNull(texture); - fd_Internal_DrawTexture2018(ref args); - } - else if (m_DrawTexture_Internal_struct == 2) - { - Internal_DrawTextureArguments_2019 args = default; - args.screenRect = screenRect; - args.sourceRect = new Rect(0, 0, 1, 1); - args.color = new Color(0.5f, 0.5f, 0.5f, 0.5f); - args.texture = UnityInternals.ObjectBaseToPtrNotNull(texture); - fd_Internal_DrawTexture2019(ref args); - } - else if (m_DrawTexture_Internal_struct == 3) - { - Internal_DrawTextureArguments_2020 args = default; - args.screenRect = screenRect; - args.sourceRect = new Rect(0, 0, 1, 1); - args.color = new Color(0.5f, 0.5f, 0.5f, 0.5f); - args.leftBorderColor = new Color(0, 0, 0, 1); - args.topBorderColor = new Color(0, 0, 0, 1); - args.rightBorderColor = new Color(0, 0, 0, 1); - args.bottomBorderColor = new Color(0, 0, 0, 1); - args.smoothCorners = true; - args.texture = UnityInternals.ObjectBaseToPtrNotNull(texture); - fd_Internal_DrawTexture2020(ref args); - } - } - - public static void DrawMeshNow(Mesh mesh, Vector3 position, Quaternion rotation) => - DrawMeshNow(mesh, position, rotation, -1); - - public static void DrawMeshNow(Mesh mesh, Vector3 position, Quaternion rotation, int materialIndex) - { - if (mesh == null) - throw new ArgumentNullException("mesh"); - Internal_DrawMeshNow1(mesh, materialIndex, position, rotation); - } - - private static void Internal_DrawMeshNow1(Mesh mesh, int subsetIndex, Vector3 position, Quaternion rotation) => - Internal_DrawMeshNow1_Injected(mesh, subsetIndex, ref position, ref rotation); - - private static void Internal_DrawMeshNow1_Injected(Mesh mesh, int subsetIndex, ref Vector3 position, ref Quaternion rotation) - { - if ((mesh == null) || (mesh.Pointer == IntPtr.Zero)) - return; - fd_Internal_DrawMeshNow1_Injected(UnityInternals.ObjectBaseToPtr(mesh), subsetIndex, ref position, ref rotation); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/HideFlags.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/HideFlags.cs deleted file mode 100644 index 86fa181e8..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/HideFlags.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MelonUnityEngine -{ - internal enum HideFlags - { - None = 0, - HideInHierarchy = 1, - HideInInspector = 2, - DontSaveInEditor = 4, - NotEditable = 8, - DontSaveInBuild = 16, - DontUnloadUnusedAsset = 32, - DontSave = 52, - HideAndDontSave = 61 - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/ImageConversion.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/ImageConversion.cs deleted file mode 100644 index 50843ca85..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/ImageConversion.cs +++ /dev/null @@ -1,40 +0,0 @@ -using MelonLoader; -using System; -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal static class ImageConversion - { - private delegate bool ImageConversion_LoadImage_Delegate(IntPtr tex, IntPtr data, bool markNonReadable); - private static ImageConversion_LoadImage_Delegate ImageConversion_LoadImage; - - static ImageConversion() - { - IntPtr ptr = UnityInternals.ResolveICall("UnityEngine.ImageConversion::LoadImage(UnityEngine.Texture2D,System.Byte[],System.Boolean)"); - if (ptr != IntPtr.Zero) - ImageConversion_LoadImage = (ImageConversion_LoadImage_Delegate)Marshal.GetDelegateForFunctionPointer(ptr, typeof(ImageConversion_LoadImage_Delegate)); - else - MelonLogger.Error("Failed to resolve icall UnityEngine.ImageConversion::LoadImage(UnityEngine.Texture2D,System.Byte[],System.Boolean)"); - } - - public unsafe static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable) - { - if (ImageConversion_LoadImage == null) - { - MelonLogger.Error("Failed to run UnityEngine.ImageConversion::LoadImage(UnityEngine.Texture2D,System.Byte[],System.Boolean)"); - return false; - } - - IntPtr dataPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (uint)data.Length); - for (var i = 0; i < data.Length; i++) - { - IntPtr arrayStartPointer = (IntPtr)((long)dataPtr + 4 * IntPtr.Size); - ((byte*)arrayStartPointer.ToPointer())[i] = data[i]; - } - - return ImageConversion_LoadImage(tex.Pointer, dataPtr, markNonReadable); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Internal_DrawTextureArguments.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Internal_DrawTextureArguments.cs deleted file mode 100644 index 6fb630c01..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Internal_DrawTextureArguments.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; - -namespace MelonUnityEngine -{ - // 2017.2.0 - [StructLayout(LayoutKind.Sequential)] - internal struct Internal_DrawTextureArguments_2017 - { - public Rect screenRect; - public Rect sourceRect; - public int leftBorder; - public int rightBorder; - public int topBorder; - public int bottomBorder; - public Color32 color; - public Vector4 borderWidths; - public float cornerRadius; - public int pass; - public IntPtr texture; - public IntPtr mat; - } - - // 2017.3.0, 2018.1.0 - [StructLayout(LayoutKind.Sequential)] - internal struct Internal_DrawTextureArguments_2018 - { - public Rect screenRect; - public Rect sourceRect; - public int leftBorder; - public int rightBorder; - public int topBorder; - public int bottomBorder; - public Color32 color; - public Vector4 borderWidths; - public Vector4 cornerRadius; // Int32 -> Vector4 - public int pass; - public IntPtr texture; - public IntPtr mat; - } - - // 2018.2.0, 2019.1.0 - [StructLayout(LayoutKind.Sequential)] - internal struct Internal_DrawTextureArguments_2019 - { - public Rect screenRect; - public Rect sourceRect; - public int leftBorder; - public int rightBorder; - public int topBorder; - public int bottomBorder; - public Color color; // Color32 -> Color - public Vector4 borderWidths; - public Vector4 cornerRadius; - public int pass; - public IntPtr texture; - public IntPtr mat; - } - - // 2019.3.0, 2020.1.0 - [StructLayout(LayoutKind.Sequential)] - internal struct Internal_DrawTextureArguments_2020 - { - public Rect screenRect; - public Rect sourceRect; - public int leftBorder; - public int rightBorder; - public int topBorder; - public int bottomBorder; - public Color leftBorderColor; // Added - public Color rightBorderColor; // Added - public Color topBorderColor; // Added - public Color bottomBorderColor; // Added - public Color color; - public Vector4 borderWidths; - public Vector4 cornerRadiuses; - public bool smoothCorners; // Added - public int pass; - public IntPtr texture; - public IntPtr mat; - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Material.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Material.cs deleted file mode 100644 index 733d8f6c0..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Material.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class Material : UnityObject - { - private delegate bool d_SetPass(IntPtr @this, int pass); - private static readonly d_SetPass m_SetPass; - - unsafe static Material() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Material"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - //m_SetPass = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "SetPass", "System.Boolean", "System.Int32"); - m_SetPass = UnityInternals.ResolveICall("UnityEngine.Material::SetPass"); - } - - public Material(IntPtr ptr) : base(ptr) { } - - public unsafe bool SetPass(int pass) - { - return m_SetPass(UnityInternals.ObjectBaseToPtrNotNull(this), pass); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Mesh.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Mesh.cs deleted file mode 100644 index 8e1dfcdc7..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Mesh.cs +++ /dev/null @@ -1,168 +0,0 @@ -using MelonLoader; -using MelonLoader.MelonStartScreen.NativeUtils; -using System; -using UnhollowerMini; -using MelonUnityEngine.Rendering; - -namespace MelonUnityEngine -{ - internal sealed class Mesh : UnityObject - { - private delegate void SetArrayForChannelImpl_2017(IntPtr @this, int channel, int format, int dim, IntPtr values, int arraySize); - private delegate void SetArrayForChannelImpl_2019(IntPtr @this, int channel, int format, int dim, IntPtr values, int arraySize, int valuesStart, int valuesCount); - private delegate void SetArrayForChannelImpl_2020(IntPtr @this, int channel, int format, int dim, IntPtr values, int arraySize, int valuesStart, int valuesCount, int updateFlags); - - private static readonly IntPtr m_ctor; - private static readonly IntPtr m_set_triangles; - private static readonly IntPtr m_RecalculateBounds; - - private static readonly SetArrayForChannelImpl_2017 m_SetArrayForChannelImpl_2017; - private static readonly SetArrayForChannelImpl_2019 m_SetArrayForChannelImpl_2019; - private static readonly SetArrayForChannelImpl_2020 m_SetArrayForChannelImpl_2020; - private static readonly int type_SetArrayForChannelImpl = -1; - - static Mesh() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Mesh"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - m_ctor = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, ".ctor", "System.Void"); - - m_set_triangles = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "set_triangles", "System.Void", "System.Int32[]"); - m_RecalculateBounds = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "RecalculateBounds", "System.Void"); - - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2020.1.0" })) - { - m_SetArrayForChannelImpl_2020 = UnityInternals.ResolveICall("UnityEngine.Mesh::SetArrayForChannelImpl"); - type_SetArrayForChannelImpl = 2; - } - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2019.3.0" })) - { - m_SetArrayForChannelImpl_2019 = UnityInternals.ResolveICall("UnityEngine.Mesh::SetArrayForChannelImpl"); - type_SetArrayForChannelImpl = 1; - } - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2017.1.0" })) - { - m_SetArrayForChannelImpl_2017 = UnityInternals.ResolveICall("UnityEngine.Mesh::SetArrayForChannelImpl"); - type_SetArrayForChannelImpl = 0; - } - } - - public Mesh(IntPtr ptr) : base(ptr) { } - - public unsafe Mesh() : base(UnityInternals.object_new(InternalClassPointerStore.NativeClassPtr)) - { - IntPtr returnedException = default; - UnityInternals.runtime_invoke(m_ctor, UnityInternals.ObjectBaseToPtrNotNull(this), (void**)0, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - - private unsafe void SetArrayForChannelImpl(int channel, IntPtr values, int channelDimensions, int valuesCount) - { - if (type_SetArrayForChannelImpl == 0) - m_SetArrayForChannelImpl_2017(UnityInternals.ObjectBaseToPtrNotNull(this), channel, 0 /* float */, channelDimensions, values, valuesCount); - else if (type_SetArrayForChannelImpl == 1) - m_SetArrayForChannelImpl_2019(UnityInternals.ObjectBaseToPtrNotNull(this), channel, 0 /* float */, channelDimensions, values, valuesCount, 0, valuesCount); - else if (type_SetArrayForChannelImpl == 2) - m_SetArrayForChannelImpl_2020(UnityInternals.ObjectBaseToPtrNotNull(this), channel, 0 /* float */, channelDimensions, values, valuesCount, 0, valuesCount, 0 /* MeshUpdateFlags.Default */); - else - throw new NotImplementedException("SetArrayForChannel isn't implemented for this version of Unity"); - } - - - public unsafe Vector3[] vertices - { - set - { - int valuesCount = value.Length; - - IntPtr valueArrayPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (ulong)valuesCount); - for (var i = 0; i < valuesCount; i++) - ((Vector3*)((long)valueArrayPtr + 4 * IntPtr.Size))[i] = value[i]; - - SetArrayForChannelImpl(VertexAttribute.Vertex, valueArrayPtr, 3, valuesCount); - } - } - - public unsafe Vector3[] normals - { - set - { - int valuesCount = value.Length; - - IntPtr valueArrayPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (ulong)valuesCount); - for (var i = 0; i < valuesCount; i++) - ((Vector3*)((long)valueArrayPtr + 4 * IntPtr.Size))[i] = value[i]; - - SetArrayForChannelImpl(VertexAttribute.Normal, valueArrayPtr, 3, valuesCount); - } - } - - public unsafe Vector4[] tangents - { - set - { - int valuesCount = value.Length; - - IntPtr valueArrayPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (ulong)valuesCount); - for (var i = 0; i < valuesCount; i++) - ((Vector4*)((long)valueArrayPtr + 4 * IntPtr.Size))[i] = value[i]; - - SetArrayForChannelImpl(VertexAttribute.Tangent, valueArrayPtr, 4, valuesCount); - } - } - - public unsafe Vector2[] uv - { - set - { - int valuesCount = value.Length; - - IntPtr valueArrayPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (ulong)valuesCount); - for (var i = 0; i < valuesCount; i++) - ((Vector2*)((long)valueArrayPtr + 4 * IntPtr.Size))[i] = value[i]; - - SetArrayForChannelImpl(VertexAttribute.TexCoord0, valueArrayPtr, 2, valuesCount); - } - } - - public unsafe Color[] colors - { - set - { - int valuesCount = value.Length; - - IntPtr valueArrayPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (ulong)valuesCount); - for (var i = 0; i < valuesCount; i++) - ((Color*)((long)valueArrayPtr + 4 * IntPtr.Size))[i] = value[i]; - - SetArrayForChannelImpl(VertexAttribute.Color, valueArrayPtr, 4, valuesCount); - } - } - - public unsafe int[] triangles - { - set - { - UnityInternals.ObjectBaseToPtrNotNull(this); - IntPtr valueArrayPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (ulong)value.Length); - for (var i = 0; i < value.Length; i++) - ((int*)((long)valueArrayPtr + 4 * IntPtr.Size))[i] = value[i]; - void** ptr = stackalloc void*[1]; - ptr[0] = (void*)valueArrayPtr; - IntPtr returnedException = default; - IntPtr intPtr = UnityInternals.runtime_invoke(m_set_triangles, UnityInternals.ObjectBaseToPtrNotNull(this), ptr, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - } - - public unsafe void RecalculateBounds() - { - UnityInternals.ObjectBaseToPtrNotNull(this); - IntPtr returnedException = default; - UnityInternals.runtime_invoke(m_RecalculateBounds, UnityInternals.ObjectBaseToPtrNotNull(this), (void**)0, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Quaternion.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Quaternion.cs deleted file mode 100644 index ab8ffa03b..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Quaternion.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Explicit)] - internal struct Quaternion - { - [FieldOffset(0)] - public float x; - [FieldOffset(4)] - public float y; - [FieldOffset(8)] - public float z; - [FieldOffset(12)] - public float w; - - static Quaternion() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Quaternion"); - } - - public static Quaternion identity => new Quaternion(); - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Rect.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Rect.cs deleted file mode 100644 index 17cf00d19..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Rect.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Explicit)] - internal struct Rect - { - [FieldOffset(0)] - public float m_XMin; - [FieldOffset(4)] - public float m_YMin; - [FieldOffset(8)] - public float m_Width; - [FieldOffset(12)] - public float m_Height; - - static Rect() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Rect"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - } - - public Rect(int x, int y, int width, int height) - { - m_XMin = x; - m_YMin = y; - m_Width = width; - m_Height = height; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Resources.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Resources.cs deleted file mode 100644 index 2dadb77f3..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Resources.cs +++ /dev/null @@ -1,41 +0,0 @@ -using MelonLoader; -using System; -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class Resources - { - private static readonly IntPtr m_GetBuiltinResource; - - static Resources() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Resources"); - //UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - m_GetBuiltinResource = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "GetBuiltinResource", "UnityEngine.Object", "System.Type", "System.String"); - - } - - public unsafe static IntPtr GetBuiltinResource(Il2CppSystem.Type type, string path) - { - void** ptr = stackalloc void*[2]; - ptr[0] = (void*)UnityInternals.ObjectBaseToPtr(type); - ptr[1] = (void*)UnityInternals.ManagedStringToInternal(path); - IntPtr returnedException = default; - MelonDebug.Msg("Calling runtime_invoke for GetBuiltinResource"); - IntPtr objectPointer = UnityInternals.runtime_invoke(m_GetBuiltinResource, IntPtr.Zero, ptr, ref returnedException); - MelonDebug.Msg("returnedException: " + returnedException + ", objectPointer: " + objectPointer); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - return objectPointer; - } - - public static T GetBuiltinResource(string path) where T : InternalObjectBase - { - MelonDebug.Msg("GetBuiltinResource"); - IntPtr ptr = GetBuiltinResource(InternalType.Of(), path); - return ptr != IntPtr.Zero ? (T)typeof(T).GetConstructor(new[] { typeof(IntPtr) }).Invoke(new object[] { ptr }) : null; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Screen.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Screen.cs deleted file mode 100644 index 259486606..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Screen.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class Screen - { - private static IntPtr m_get_width; - private static IntPtr m_get_height; - - static Screen() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Screen"); - m_get_width = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "get_width", "System.Int32"); - m_get_height = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "get_height", "System.Int32"); - } - - public unsafe static int width - { - get - { - IntPtr* param = null; - IntPtr returnedException = IntPtr.Zero; - IntPtr intPtr = UnityInternals.runtime_invoke(m_get_width, IntPtr.Zero, (void**)param, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - return *(int*)UnityInternals.object_unbox(intPtr); - } - } - - public unsafe static int height - { - get - { - IntPtr* param = null; - IntPtr returnedException = IntPtr.Zero; - IntPtr intPtr = UnityInternals.runtime_invoke(m_get_height, IntPtr.Zero, (void**)param, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - return *(int*)UnityInternals.object_unbox(intPtr); - } - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/SystemInfo.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/SystemInfo.cs deleted file mode 100644 index 9f00acc27..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/SystemInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MelonLoader; -using MelonLoader.MelonStartScreen.NativeUtils; -using UnhollowerMini; - -namespace MelonUnityEngine.CoreModule -{ - internal sealed class SystemInfo - { - private delegate uint d_GetGraphicsDeviceType(); - private static readonly d_GetGraphicsDeviceType m_GetGraphicsDeviceType; - - unsafe static SystemInfo() - { - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2018.1.0" })) - m_GetGraphicsDeviceType = UnityInternals.ResolveICall("UnityEngine.SystemInfo::GetGraphicsDeviceType"); - else - m_GetGraphicsDeviceType = UnityInternals.ResolveICall("UnityEngine.SystemInfo::get_graphicsDeviceType"); - } - - public static uint GetGraphicsDeviceType() => - m_GetGraphicsDeviceType(); - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture.cs deleted file mode 100644 index b6b681578..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MelonLoader; -using MelonLoader.MelonStartScreen.NativeUtils; -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class Texture : UnityObject - { - private delegate int GetDataWidthDelegate(IntPtr @this); - private delegate int GetDataHeightDelegate(IntPtr @this); - private delegate int set_filterModeDelegate(IntPtr @this, FilterMode filterMode); - - private static readonly GetDataWidthDelegate getDataWidth; - private static readonly GetDataHeightDelegate getDataHeight; - private static readonly set_filterModeDelegate set_filterMode_; - - static Texture() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Texture"); - - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2018.1.0" })) - { - getDataWidth = UnityInternals.ResolveICall("UnityEngine.Texture::GetDataWidth"); - getDataHeight = UnityInternals.ResolveICall("UnityEngine.Texture::GetDataHeight"); - } - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2017.1.0" })) - { - getDataWidth = UnityInternals.ResolveICall("UnityEngine.Texture::Internal_GetWidth"); - getDataHeight = UnityInternals.ResolveICall("UnityEngine.Texture::Internal_GetHeight"); - } - set_filterMode_ = UnityInternals.ResolveICall("UnityEngine.Texture::set_filterMode"); - } - - public Texture(IntPtr ptr) : base(ptr) { } - - public int width => getDataWidth(UnityInternals.ObjectBaseToPtrNotNull(this)); - public int height => getDataHeight(UnityInternals.ObjectBaseToPtrNotNull(this)); - - public FilterMode filterMode - { - set => set_filterMode_(UnityInternals.ObjectBaseToPtrNotNull(this), value); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture2D.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture2D.cs deleted file mode 100644 index 277e0571d..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Texture2D.cs +++ /dev/null @@ -1,98 +0,0 @@ -using MelonLoader; -using MelonLoader.MelonStartScreen.NativeUtils; -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class Texture2D : Texture - { - private delegate void SetPixelsImplDelegate_2017(IntPtr @this, int x, int y, int w, int h, IntPtr pixel, int miplevel); - private delegate void SetPixelsImplDelegate_2018(IntPtr @this, int x, int y, int w, int h, IntPtr pixel, int miplevel, int frame); - - private static readonly IntPtr m_get_whiteTexture; - private static readonly IntPtr m_ctor; - private static readonly SetPixelsImplDelegate_2017 m_SetPixelsImpl_2017; - private static readonly SetPixelsImplDelegate_2018 m_SetPixelsImpl_2018; - private static readonly IntPtr m_Apply; - - private static readonly int type_SetPixelsImpl = -1; - - static Texture2D() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Texture2D"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - m_ctor = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, ".ctor", "System.Void", "System.Int32", "System.Int32"); - - m_get_whiteTexture = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "get_whiteTexture", "UnityEngine.Texture2D"); - - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2018.1.0" })) { - type_SetPixelsImpl = 1; - m_SetPixelsImpl_2018 = UnityInternals.ResolveICall("UnityEngine.Texture2D::SetPixelsImpl"); - } - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2017.1.0" })) - { - type_SetPixelsImpl = 0; - m_SetPixelsImpl_2017 = UnityInternals.ResolveICall("UnityEngine.Texture2D::SetPixels"); - } - - m_Apply = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "Apply", "System.Void"); - } - - public Texture2D(IntPtr ptr) : base(ptr) { } - - public unsafe static Texture2D whiteTexture - { - get - { - IntPtr returnedException = IntPtr.Zero; - IntPtr intPtr = UnityInternals.runtime_invoke(m_get_whiteTexture, IntPtr.Zero, (void**)0, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - return intPtr == IntPtr.Zero ? null : new Texture2D(intPtr); - } - } - - public unsafe Texture2D(int width, int height) : base(UnityInternals.object_new(InternalClassPointerStore.NativeClassPtr)) - { - void** args = stackalloc void*[2]; - args[0] = &width; - args[1] = &height; - IntPtr returnedException = default; - UnityInternals.runtime_invoke(m_ctor, UnityInternals.ObjectBaseToPtrNotNull(this), args, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - - public unsafe void SetPixels(Color[] colors) - { - SetPixels(0, 0, width, height, colors, 0); - } - - public unsafe void SetPixels(int x, int y, int blockWidth, int blockHeight, Color[] colors, int miplevel = 0) - { - SetPixelsImpl(x, y, blockWidth, blockHeight, colors, miplevel, 0); - } - - public unsafe void SetPixelsImpl(int x, int y, int w, int h, Color[] pixel, int miplevel, int frame) - { - IntPtr pixelArrayPtr = UnityInternals.array_new(InternalClassPointerStore.NativeClassPtr, (uint)pixel.Length); - for (var i = 0; i < pixel.Length; i++) - { - IntPtr arrayStartPointer = (IntPtr)((long)pixelArrayPtr + 4 * IntPtr.Size); - ((Color*)arrayStartPointer.ToPointer())[i] = pixel[i]; - } - - if (type_SetPixelsImpl == 0) - m_SetPixelsImpl_2017(UnityInternals.ObjectBaseToPtrNotNull(this), x, y, w, h, pixelArrayPtr, miplevel); - else if (type_SetPixelsImpl == 1) - m_SetPixelsImpl_2018(UnityInternals.ObjectBaseToPtrNotNull(this), x, y, w, h, pixelArrayPtr, miplevel, frame); - } - - public unsafe void Apply() - { - IntPtr returnedException = default; - UnityInternals.runtime_invoke(m_Apply, UnityInternals.ObjectBaseToPtrNotNull(this), (void**)0, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityDebug.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityDebug.cs deleted file mode 100644 index ee4b45df9..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityDebug.cs +++ /dev/null @@ -1,24 +0,0 @@ -using MelonLoader; -using System; -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal static class UnityDebug - { - private delegate bool get_isDebugBuild_Delegate(); - private static get_isDebugBuild_Delegate get_isDebugBuild_Ptr; - - static UnityDebug() - { - IntPtr ptr = UnityInternals.ResolveICall("UnityEngine.Debug::get_isDebugBuild"); - if (ptr != IntPtr.Zero) - get_isDebugBuild_Ptr = (get_isDebugBuild_Delegate)Marshal.GetDelegateForFunctionPointer(ptr, typeof(get_isDebugBuild_Delegate)); - else - MelonLogger.Error("Failed to resolve icall UnityEngine.Debug::get_isDebugBuild"); - } - - internal static bool isDebugBuild { get => get_isDebugBuild_Ptr(); } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityObject.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityObject.cs deleted file mode 100644 index a7fbae4dd..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/UnityObject.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class UnityObject : InternalObjectBase - { - private delegate HideFlags get_hideFlags_Delegate(IntPtr obj); - private static get_hideFlags_Delegate m_get_hideFlags; - private delegate void set_hideFlags_Delegate(IntPtr obj, HideFlags hideFlags); - private static set_hideFlags_Delegate m_set_hideFlags; - - private static IntPtr m_DestroyImmediate; - private static IntPtr m_DontDestroyOnLoad; - unsafe static UnityObject() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Object"); - //UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - m_DestroyImmediate = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "DestroyImmediate", "System.Void", "UnityEngine.Object"); - m_DontDestroyOnLoad = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "DontDestroyOnLoad", "System.Void", "UnityEngine.Object"); - - m_get_hideFlags = UnityInternals.ResolveICall("UnityEngine.Object::get_hideFlags(UnityEngine.Object)"); - m_set_hideFlags = UnityInternals.ResolveICall("UnityEngine.Object::set_hideFlags(UnityEngine.Object)"); - } - - public UnityObject(IntPtr ptr) : base(ptr) { } - - unsafe public HideFlags hideFlags - { - get - { - if (Pointer == IntPtr.Zero) - return HideFlags.None; - return m_get_hideFlags(Pointer); - } - set - { - if (Pointer == IntPtr.Zero) - return; - m_set_hideFlags(Pointer, value); - } - } - - unsafe public void DestroyImmediate() - { - if (Pointer == IntPtr.Zero) - return; - - void** args = stackalloc void*[1]; - args[0] = Pointer.ToPointer(); - - IntPtr returnedException = IntPtr.Zero; - UnityInternals.runtime_invoke(m_DestroyImmediate, IntPtr.Zero, args, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - - unsafe public void DontDestroyOnLoad() - { - if (Pointer == IntPtr.Zero) - return; - - void** args = stackalloc void*[1]; - args[0] = Pointer.ToPointer(); - - IntPtr returnedException = IntPtr.Zero; - UnityInternals.runtime_invoke(m_DontDestroyOnLoad, IntPtr.Zero, args, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector2.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector2.cs deleted file mode 100644 index 9ad85ed7f..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector2.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Explicit)] - internal struct Vector2 - { - [FieldOffset(0)] - public float x; - [FieldOffset(4)] - public float y; - - static Vector2() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Vector2"); - } - - public Vector2(float x, float y) - { - this.x = x; - this.y = y; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector3.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector3.cs deleted file mode 100644 index 1089a10f0..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector3.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Explicit)] - internal struct Vector3 - { - [FieldOffset(0)] - public float x; - [FieldOffset(4)] - public float y; - [FieldOffset(8)] - public float z; - - static Vector3() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Vector3"); - } - - public static Vector3 zero => new Vector3(); - - public Vector3(float x, float y, float z) - { - this.x = x; - this.y = y; - this.z = z; - } - - public static Vector3 operator*(Vector3 a, float d) - { - return new Vector3(a.x * d, a.y * d, a.z * d); - } - - public override string ToString() - { - return $"{x} {y} {z}"; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector4.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector4.cs deleted file mode 100644 index d0b4d911b..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/Vector4.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Explicit)] - internal struct Vector4 - { - [FieldOffset(0)] - public float x; - [FieldOffset(4)] - public float y; - [FieldOffset(8)] - public float z; - [FieldOffset(12)] - public float w; - - static Vector4() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.CoreModule.dll", "UnityEngine", "Vector4"); - } - - public static explicit operator Vector2(Vector4 src) => new Vector2(src.x, src.y); - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/VertexAttribute.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/VertexAttribute.cs deleted file mode 100644 index e4fa48bd0..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/VertexAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MelonLoader.MelonStartScreen.NativeUtils; - -namespace MelonUnityEngine.Rendering -{ - static class VertexAttribute - { - public static int Vertex = 0; - public static int Normal = 1; - - [NativeFieldValue(01, NativeSignatureFlags.None, 7, "2017.1.0")] - [NativeFieldValue(02, NativeSignatureFlags.None, 2, "2018.1.0")] - public static int Tangent = 0; - - [NativeFieldValue(01, NativeSignatureFlags.None, 2, "2017.1.0")] - [NativeFieldValue(02, NativeSignatureFlags.None, 3, "2018.1.0")] - public static int Color = 0; - - [NativeFieldValue(01, NativeSignatureFlags.None, 3, "2017.1.0")] - [NativeFieldValue(02, NativeSignatureFlags.None, 4, "2018.1.0")] - public static int TexCoord0 = 0; - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/VerticalWrapMode.cs b/Dependencies/MelonStartScreen/UnityEngine/CoreModule/VerticalWrapMode.cs deleted file mode 100644 index 20c510df6..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/CoreModule/VerticalWrapMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MelonUnityEngine -{ - internal enum VerticalWrapMode - { - Truncate, - Overflow - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/Font.cs b/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/Font.cs deleted file mode 100644 index 790a07084..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/Font.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class Font : UnityObject - { - private static IntPtr m_get_material; - - static Font() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - m_get_material = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "get_material", "UnityEngine.Material"); - } - - public Font(IntPtr ptr) : base(ptr) { } - - public unsafe Material material - { - get - { - UnityInternals.ObjectBaseToPtrNotNull(this); - IntPtr returnedException = default; - IntPtr intPtr = UnityInternals.runtime_invoke(Font.m_get_material, UnityInternals.ObjectBaseToPtrNotNull(this), (void**)0, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - return (intPtr != IntPtr.Zero) ? new Material(intPtr) : null; - } - // set - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/FontStyle.cs b/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/FontStyle.cs deleted file mode 100644 index d9f5764f4..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/FontStyle.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MelonUnityEngine -{ - internal enum FontStyle - { - Normal, - Bold, - Italic, - BoldAndItalic - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextAnchor.cs b/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextAnchor.cs deleted file mode 100644 index 5b997cf00..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextAnchor.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MelonUnityEngine -{ - internal enum TextAnchor - { - UpperLeft, - UpperCenter, - UpperRight, - MiddleLeft, - MiddleCenter, - MiddleRight, - LowerLeft, - LowerCenter, - LowerRight - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerationSettings.cs b/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerationSettings.cs deleted file mode 100644 index f8983e9cd..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerationSettings.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - internal class TextGenerationSettings : InternalObjectBase - { - private static readonly int classsize; - - private static readonly IntPtr f_font; - private static readonly IntPtr f_color; - private static readonly IntPtr f_fontSize; - private static readonly IntPtr f_lineSpacing; - private static readonly IntPtr f_richText; - private static readonly IntPtr f_scaleFactor; - private static readonly IntPtr f_fontStyle; - private static readonly IntPtr f_textAnchor; - //private static readonly IntPtr f_alignByGeometry; - //private static readonly IntPtr f_resizeTextForBestFit; - //private static readonly IntPtr f_resizeTextMinSize; - //private static readonly IntPtr f_resizeTextMaxSize; - //private static readonly IntPtr f_updateBounds; - private static readonly IntPtr f_verticalOverflow; - //private static readonly IntPtr f_horizontalOverflow; - private static readonly IntPtr f_generationExtents; - private static readonly IntPtr f_pivot; - //private static readonly IntPtr f_generateOutOfBounds; - - static TextGenerationSettings() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "TextGenerationSettings"); - //UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - uint align = 0; - classsize = UnityInternals.class_value_size(InternalClassPointerStore.NativeClassPtr, ref align); - - - f_font = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "font"); - f_color = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "color"); - f_fontSize = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "fontSize"); - f_lineSpacing = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "lineSpacing"); - f_richText = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "richText"); - f_scaleFactor = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "scaleFactor"); - f_fontStyle = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "fontStyle"); - f_textAnchor = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "textAnchor"); - //f_alignByGeometry = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "alignByGeometry"); - //f_resizeTextForBestFit = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "resizeTextForBestFit"); - //f_resizeTextMinSize = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "resizeTextMinSize"); - //f_resizeTextMaxSize = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "resizeTextMaxSize"); - //f_updateBounds = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "updateBounds"); - f_verticalOverflow = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "verticalOverflow"); - //f_horizontalOverflow = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "horizontalOverflow"); - f_generationExtents = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "generationExtents"); - f_pivot = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "pivot"); - //f_generateOutOfBounds = UnityInternals.GetField(InternalClassPointerStore.NativeClassPtr, "generateOutOfBounds"); - } - - public TextGenerationSettings(IntPtr ptr) : base(ptr) { } - - public unsafe TextGenerationSettings() - { - byte** data = stackalloc byte*[classsize]; - IntPtr pointer = UnityInternals.value_box(InternalClassPointerStore.NativeClassPtr, (IntPtr)data); - myGcHandle = UnityInternals.gchandle_new(pointer, false); - } - - public unsafe Font font - { - get - { - IntPtr intPtr = *(IntPtr*)((uint)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_font)); - return (intPtr != IntPtr.Zero) ? new Font(intPtr) : null; - } - set => *(IntPtr*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_font)) = UnityInternals.ObjectBaseToPtr(value); - } - - public unsafe Color color - { - get => *(Color*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_color)); - set => *(Color*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_color)) = value; - } - - public unsafe int fontSize - { - get => *(int*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_fontSize)); - set => *(int*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_fontSize)) = value; - } - - public unsafe float lineSpacing - { - get => *(float*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_lineSpacing)); - set => *(float*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_lineSpacing)) = value; - } - - public unsafe bool richText - { - get => *(bool*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_richText)); - set => *(bool*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_richText)) = value; - } - - public unsafe float scaleFactor - { - get => *(float*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_scaleFactor)); - set => *(float*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_scaleFactor)) = value; - } - - public unsafe FontStyle fontStyle - { - get => *(FontStyle*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_fontStyle)); - set => *(FontStyle*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_fontStyle)) = value; - } - - public unsafe TextAnchor textAnchor - { - get => *(TextAnchor*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_textAnchor)); - set => *(TextAnchor*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_textAnchor)) = value; - } - - public unsafe VerticalWrapMode verticalOverflow - { - get => *(VerticalWrapMode*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_verticalOverflow)); - set => *(VerticalWrapMode*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_verticalOverflow)) = value; - } - - public unsafe Vector2 generationExtents - { - get => *(Vector2*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_generationExtents)); - set => *(Vector2*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_generationExtents)) = value; - } - - public unsafe Vector2 pivot - { - get => *(Vector2*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_pivot)); - set => *(Vector2*)((ulong)UnityInternals.ObjectBaseToPtrNotNull(this) + UnityInternals.field_get_offset(f_pivot)) = value; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerator.cs b/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerator.cs deleted file mode 100644 index 6cb6b2a63..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/TextGenerator.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using UnhollowerMini; - -namespace MelonUnityEngine -{ - class TextGenerator : InternalObjectBase - { - private delegate int get_vertexCountDelegate(IntPtr @this); - private delegate IntPtr GetVerticesArrayDelegate(IntPtr @this); - - private static readonly IntPtr m_ctor; - private static readonly IntPtr m_Populate; - private static readonly get_vertexCountDelegate fd_get_vertexCount; - private static readonly GetVerticesArrayDelegate fd_GetVerticesArray; - - static TextGenerator() - { - InternalClassPointerStore.NativeClassPtr = UnityInternals.GetClass("UnityEngine.TextRenderingModule.dll", "UnityEngine", "TextGenerator"); - UnityInternals.runtime_class_init(InternalClassPointerStore.NativeClassPtr); - - m_ctor = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, ".ctor", "System.Void"); - - m_Populate = UnityInternals.GetMethod(InternalClassPointerStore.NativeClassPtr, "Populate", "System.Boolean", "System.String", "UnityEngine.TextGenerationSettings"); - - fd_get_vertexCount = UnityInternals.ResolveICall("UnityEngine.TextGenerator::get_vertexCount"); - fd_GetVerticesArray = UnityInternals.ResolveICall("UnityEngine.TextGenerator::GetVerticesArray"); - } - - public TextGenerator(IntPtr ptr) : base(ptr) { } - - public unsafe TextGenerator() : this(UnityInternals.object_new(InternalClassPointerStore.NativeClassPtr)) - { - IntPtr returnedException = default; - UnityInternals.runtime_invoke(m_ctor, UnityInternals.ObjectBaseToPtrNotNull(this), (void**)0, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - } - - public unsafe bool Populate(string str, TextGenerationSettings settings) - { - void** args = stackalloc void*[2]; - args[0] = (void*)UnityInternals.ManagedStringToInternal(str); - args[1] = (void*)UnityInternals.object_unbox(UnityInternals.ObjectBaseToPtrNotNull(settings)); - IntPtr returnedException = default; - IntPtr obj = UnityInternals.runtime_invoke(m_Populate, UnityInternals.ObjectBaseToPtrNotNull(this), args, ref returnedException); - Il2CppException.RaiseExceptionIfNecessary(returnedException); - return *(bool*)UnityInternals.object_unbox(obj); - } - - public int vertexCount => fd_get_vertexCount(UnityInternals.ObjectBaseToPtrNotNull(this)); - - public unsafe UIVertexWrapper[] GetVerticesArray() - { - IntPtr intPtr = fd_GetVerticesArray(UnityInternals.ObjectBaseToPtrNotNull(this)); - if (intPtr == IntPtr.Zero) return null; - UIVertexWrapper[] arr = new UIVertexWrapper[UnityInternals.array_length(intPtr)]; - for (int i = 0; i < arr.Length; ++i) - arr[i] = new UIVertexWrapper((IntPtr)((long)intPtr + 4 * IntPtr.Size + i * UIVertexWrapper.sizeOfElement)); - // arr[i] = ( (UIVertex*)((long)intPtr + 4 * IntPtr.Size) )[i]; - // arr[i] = *( (UIVertex*)((long)intPtr + 4 * IntPtr.Size) + (i * sizeof(UIVertex))) - return arr; - } - } -} diff --git a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/UIVertex.cs b/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/UIVertex.cs deleted file mode 100644 index a7a844c3f..000000000 --- a/Dependencies/MelonStartScreen/UnityEngine/TextRenderingModule/UIVertex.cs +++ /dev/null @@ -1,108 +0,0 @@ -using MelonLoader; -using MelonLoader.MelonStartScreen.NativeUtils; -using System; -using System.Runtime.InteropServices; - -namespace MelonUnityEngine -{ - [StructLayout(LayoutKind.Sequential)] - internal struct UIVertex_2020 - { - public Vector3 position; - public Vector3 normal; - public Vector4 tangent; - public Color32 color; - public Vector4 uv0; - public Vector4 uv1; - public Vector4 uv2; - public Vector4 uv3; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct UIVertex_2018 - { - public Vector3 position; - public Vector3 normal; - public Vector4 tangent; - public Color32 color; - public Vector2 uv0; - public Vector2 uv1; - public Vector2 uv2; - public Vector2 uv3; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct UIVertex_2017 - { - public Vector3 position; - public Vector3 normal; - public Color32 color; - public Vector2 uv0; - public Vector2 uv1; - public Vector2 uv2; - public Vector2 uv3; - public Vector4 tangent; - } - - internal struct UIVertexWrapper - { - private static readonly int mode = -1; - public static readonly int sizeOfElement = 0; - - private IntPtr ptr; - - unsafe static UIVertexWrapper() - { - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2020.2.0", "2021.1.0" })) - { - mode = 2; - sizeOfElement = sizeof(UIVertex_2020); - } - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2018.1.0" })) - { - mode = 1; - sizeOfElement = sizeof(UIVertex_2018); - } - else if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new string[] { "2017.2.0" })) - { - mode = 0; - sizeOfElement = sizeof(UIVertex_2017); - } - } - - public UIVertexWrapper(IntPtr ptr) - { - this.ptr = ptr; - } - - public unsafe Vector3 position => - mode == 2 ? (*(UIVertex_2020*)ptr).position : - mode == 1 ? (*(UIVertex_2018*)ptr).position : - mode == 0 ? (*(UIVertex_2017*)ptr).position : - throw new Exception("UIVertex mode not set"); - - public unsafe Vector3 normal => - mode == 2 ? (*(UIVertex_2020*)ptr).normal : - mode == 1 ? (*(UIVertex_2018*)ptr).normal : - mode == 0 ? (*(UIVertex_2017*)ptr).normal : - throw new Exception("UIVertex mode not set"); - - public unsafe Vector4 tangent => - mode == 2 ? (*(UIVertex_2020*)ptr).tangent : - mode == 1 ? (*(UIVertex_2018*)ptr).tangent : - mode == 0 ? (*(UIVertex_2017*)ptr).tangent : - throw new Exception("UIVertex mode not set"); - - public unsafe Color32 color => - mode == 2 ? (*(UIVertex_2020*)ptr).color : - mode == 1 ? (*(UIVertex_2018*)ptr).color : - mode == 0 ? (*(UIVertex_2017*)ptr).color : - throw new Exception("UIVertex mode not set"); - - public unsafe Vector2 uv0 => - mode == 2 ? (Vector2)(*(UIVertex_2020*)ptr).uv0 : - mode == 1 ? (*(UIVertex_2018*)ptr).uv0 : - mode == 0 ? (*(UIVertex_2017*)ptr).uv0 : - throw new Exception("UIVertex mode not set"); - } -} diff --git a/Dependencies/MelonStartScreen/UnityPlayer/GfxDevice.cs b/Dependencies/MelonStartScreen/UnityPlayer/GfxDevice.cs deleted file mode 100644 index e6098c8f6..000000000 --- a/Dependencies/MelonStartScreen/UnityPlayer/GfxDevice.cs +++ /dev/null @@ -1,104 +0,0 @@ -using MelonLoader; -using MelonLoader.NativeUtils; -using MelonLoader.MelonStartScreen.NativeUtils; -using System; -using System.Runtime.InteropServices; -using UnhollowerMini; - -namespace UnityPlayer -{ - internal class GfxDevice - { - private delegate void PresentFrameDelegate(); - private delegate void WaitForLastPresentationAndGetTimestampDelegate(IntPtr gfxDevice); - private delegate IntPtr GetRealGfxDeviceDelegate(); - -#pragma warning disable 0649 - #region m_PresentFrame Signatures - [NativeSignature(01, NativeSignatureFlags.X86, "e8 ?? ?? ?? ?? 85 c0 74 12 e8 ?? ?? ?? ?? 8b ?? 8b ?? 8b 42 70 ff d0 84 c0 75", "2017.1.0", "5.6.0", "2017.1.0")] - [NativeSignature(02, NativeSignatureFlags.X86, "55 8b ec 51 e8 ?? ?? ?? ?? 85 c0 74 12 e8 ?? ?? ?? ?? 8b c8 8b 10 8b 42 ?? ff d0 84 c0 75", "2018.1.0")] - [NativeSignature(03, NativeSignatureFlags.X86, "55 8b ec 51 e8 ?? ?? ?? ?? 85 c0 74 15 e8 ?? ?? ?? ?? 8b c8 8b 10 8b 82 ?? 00 00 00 ff d0", "2018.4.9", "2019.1.0")] - [NativeSignature(04, NativeSignatureFlags.X86, "55 8b ec 51 56 e8 ?? ?? ?? ?? 8b f0 8b ce e8 ?? ?? ?? ?? e8 ?? ?? ?? ?? 85 c0 74 ?? e8", "2018.4.18", "2019.3.0", "2020.1.0")] - - [NativeSignature(01, NativeSignatureFlags.X64, "48 83 ec 28 e8 ?? ?? ?? ?? 48 85 c0 74 15 e8 ?? ?? ?? ?? 48 8b c8 48 8b 10 ff 92 e0 00 00 00 84 c0", "5.6.0", "2017.1.0")] - [NativeSignature(02, NativeSignatureFlags.X64, "48 83 ec 28 e8 ?? ?? ?? ?? 48 85 c0 74 15 e8 ?? ?? ?? ?? 48 8b c8 48 8b 10 ff 92 ?? ?? 00 00 84 c0", "2018.3.0", "2019.1.0")] // We can't use this one too early, else we match multiple functions - [NativeSignature(03, NativeSignatureFlags.X64, "40 53 48 83 ec 20 e8 ?? ?? ?? ?? 48 8b c8 48 8b d8 e8 ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 85 c0 74", "2018.4.18", "2019.3.0", "2020.1.0")] - #endregion - private static PresentFrameDelegate m_PresentFrame; - - #region m_D3D11WaitForLastPresentationAndGetTimestamp Signatures - [NativeSignature(00, NativeSignatureFlags.None, null, "2017.1.0")] - - [NativeSignature(01, NativeSignatureFlags.X86, "55 8b ec 83 ec 40 53 56 8b d9 57 89 5d fc e8 ?? ?? ?? ?? 6a 02 8b c8", "2020.2.7", "2020.3.0", "2021.1.0")] - [NativeSignature(02, NativeSignatureFlags.X86, "55 8b ec 83 ec 48 53 56 8b d9 57 89 5d fc e8 ?? ?? ?? ?? 6a 02 8b c8", "2021.1.5", "2021.2.0")] - [NativeSignature(03, NativeSignatureFlags.X86, "55 8b ec 83 ec 58 53 56 8b d9 57 89 5d fc e8 ?? ?? ?? ?? 6a 02 8b c8", "2022.1.0")] - [NativeSignature(04, NativeSignatureFlags.X86 | NativeSignatureFlags.Mono, null, "2020.3.9")] // TODO validate this with more advanced sigcheck - - [NativeSignature(01, NativeSignatureFlags.X64, "48 89 5c 24 10 56 48 81 ec 90 00 00 00 0f 29 b4 24 80 00 00 00 48 8b f1", "2020.2.7", "2020.3.0", "2021.1.0")] - [NativeSignature(02, NativeSignatureFlags.X64, "48 89 5c 24 10 56 48 81 ec b0 00 00 00 0f 29 b4 24 a0 00 00 00 48 8b f1", "2022.1.0")] - #endregion - private static WaitForLastPresentationAndGetTimestampDelegate m_D3D11WaitForLastPresentationAndGetTimestamp; - - #region m_D3D12WaitForLastPresentationAndGetTimestamp Signatures - [NativeSignature(00, NativeSignatureFlags.None, null, "2017.1.0")] - - [NativeSignature(01, NativeSignatureFlags.X86, "55 8b ec 83 ec 40 53 56 57 8b f9 89 7d f4 e8 ?? ?? ?? ?? 6a 02 8b c8", "2020.2.7", "2020.3.0", "2021.1.0")] - [NativeSignature(02, NativeSignatureFlags.X86, "55 8b ec 83 ec 48 56 57 8b f9 89 7d f0 e8 ?? ?? ?? ?? 6a 02 8b c8", "2020.3.9", "2021.1.5")] - [NativeSignature(03, NativeSignatureFlags.X86, "55 8b ec 83 ec 48 56 57 8b f9 89 7d f8 e8 ?? ?? ?? ?? 6a 02 8b c8", "2021.2.0")] - [NativeSignature(04, NativeSignatureFlags.X86, "55 8b ec 83 ec 58 56 57 8b f9 89 7d f8 e8 ?? ?? ?? ?? 6a 02 8b c8", "2022.1.0")] - - [NativeSignature(01, NativeSignatureFlags.X64, "48 89 5c 24 08 57 48 81 ec 90 00 00 00 0f 29 b4 24 80 00 00 00 48 8b d9", "2020.2.7", "2020.3.0", "2021.1.0")] - [NativeSignature(02, NativeSignatureFlags.X64, "48 89 5c 24 08 57 48 81 ec b0 00 00 00 0f 29 b4 24 a0 00 00 00 48 8b d9", "2022.1.0")] - #endregion - private static WaitForLastPresentationAndGetTimestampDelegate m_D3D12WaitForLastPresentationAndGetTimestamp; -#pragma warning restore 0649 - - private static GetRealGfxDeviceDelegate m_GetRealGfxDevice; - - static GfxDevice() - { - if (NativeSignatureResolver.IsUnityVersionOverOrEqual(MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(), new[] { "2020.2.7", "2020.3.0", "2021.1.0" })) - { - // `FrameTimingManager_CUSTOM_CaptureFrameTimings()` calls `GetRealGfxDevice()` after 4 bytes. - m_GetRealGfxDevice = (GetRealGfxDeviceDelegate)Marshal.GetDelegateForFunctionPointer( - CppUtils.ResolveRelativeInstruction( - (IntPtr)((long)UnityInternals.ResolveICall("UnityEngine.FrameTimingManager::CaptureFrameTimings") + (MelonUtils.IsGame32Bit() ? 0 : 4))), - typeof(GetRealGfxDeviceDelegate)); - } - } - - public static void PresentFrame() => - m_PresentFrame(); - - public static IntPtr GetRealGfxDevice() => - m_GetRealGfxDevice(); - - internal static void WaitForLastPresentationAndGetTimestamp(uint deviceType) - { - if (m_GetRealGfxDevice == null) - throw new NotImplementedException(); - - IntPtr gfxDevice = GetRealGfxDevice(); - if (gfxDevice == IntPtr.Zero) - throw new NotImplementedException(); - - switch (deviceType) - { - case /*DX11*/ 2: - if (m_D3D11WaitForLastPresentationAndGetTimestamp == null) - throw new NotImplementedException(); - m_D3D11WaitForLastPresentationAndGetTimestamp(gfxDevice); - break; - - case /*DX12*/ 18: - if (m_D3D12WaitForLastPresentationAndGetTimestamp == null) - throw new NotImplementedException(); - m_D3D12WaitForLastPresentationAndGetTimestamp(gfxDevice); - break; - - default: - throw new NotImplementedException(); - } - } - } -} diff --git a/Dependencies/MelonStartScreen/Windows/DropFile.cs b/Dependencies/MelonStartScreen/Windows/DropFile.cs deleted file mode 100644 index 7c9b2d4bb..000000000 --- a/Dependencies/MelonStartScreen/Windows/DropFile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Windows -{ - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - class DropFile - { - uint pFiles = 14; - public Point pt; - public bool fNC; - bool fWide = true; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 300)] // Max path size on windows is 260 - public string file = ""; - } -} diff --git a/Dependencies/MelonStartScreen/Windows/Msg.cs b/Dependencies/MelonStartScreen/Windows/Msg.cs deleted file mode 100644 index 1a17fa734..000000000 --- a/Dependencies/MelonStartScreen/Windows/Msg.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Windows -{ - [StructLayout(LayoutKind.Sequential)] - internal struct Msg - { - public IntPtr hwnd; - public WindowMessage message; - public IntPtr wParam; - public IntPtr lParam; - public uint time; - public Point pt; - } -} diff --git a/Dependencies/MelonStartScreen/Windows/Point.cs b/Dependencies/MelonStartScreen/Windows/Point.cs deleted file mode 100644 index 4d7c5feae..000000000 --- a/Dependencies/MelonStartScreen/Windows/Point.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Windows -{ - internal struct Point - { - public int x; - public int y; - } -} diff --git a/Dependencies/MelonStartScreen/Windows/User32.cs b/Dependencies/MelonStartScreen/Windows/User32.cs deleted file mode 100644 index 9778f4a1b..000000000 --- a/Dependencies/MelonStartScreen/Windows/User32.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Windows -{ - internal static class User32 - { - [DllImport("user32.dll")] - public static extern bool PeekMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); - - [DllImport("user32.dll")] - public static extern bool TranslateMessage([In] ref Msg lpMsg); - - [DllImport("user32.dll")] - public static extern IntPtr DispatchMessage([In] ref Msg lpmsg); - - [DllImport("user32.dll", SetLastError = false)] - public static extern IntPtr GetMessageExtraInfo(); - - [DllImport("user32.dll", ExactSpelling = true)] - public static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, TimerProc lpTimerFunc); - public delegate void TimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime); - - [DllImport("user32.dll", ExactSpelling = true)] - public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent); - - [DllImport("user32.dll")] - public static extern IntPtr SetClipboardData(uint uFormat, ref DropFile hMem); - } -} diff --git a/Dependencies/MelonStartScreen/Windows/WindowMessage.cs b/Dependencies/MelonStartScreen/Windows/WindowMessage.cs deleted file mode 100644 index 738ebb605..000000000 --- a/Dependencies/MelonStartScreen/Windows/WindowMessage.cs +++ /dev/null @@ -1,969 +0,0 @@ -using System; - -namespace Windows -{ - /// - /// Windows Messages - /// Defined in winuser.h from Windows SDK v6.1 - /// Documentation pulled from MSDN. - /// - internal enum WindowMessage : uint - { - /// - /// The WM_NULL message performs no operation. An application sends the WM_NULL message if it wants to post a message that the recipient window will ignore. - /// - NULL = 0x0000, - /// - /// The WM_CREATE message is sent when an application requests that a window be created by calling the CreateWindowEx or CreateWindow function. (The message is sent before the function returns.) The window procedure of the new window receives this message after the window is created, but before the window becomes visible. - /// - CREATE = 0x0001, - /// - /// The WM_DESTROY message is sent when a window is being destroyed. It is sent to the window procedure of the window being destroyed after the window is removed from the screen. - /// This message is sent first to the window being destroyed and then to the child windows (if any) as they are destroyed. During the processing of the message, it can be assumed that all child windows still exist. - /// /// - DESTROY = 0x0002, - /// - /// The WM_MOVE message is sent after a window has been moved. - /// - MOVE = 0x0003, - /// - /// The WM_SIZE message is sent to a window after its size has changed. - /// - SIZE = 0x0005, - /// - /// The WM_ACTIVATE message is sent to both the window being activated and the window being deactivated. If the windows use the same input queue, the message is sent synchronously, first to the window procedure of the top-level window being deactivated, then to the window procedure of the top-level window being activated. If the windows use different input queues, the message is sent asynchronously, so the window is activated immediately. - /// - ACTIVATE = 0x0006, - /// - /// The WM_SETFOCUS message is sent to a window after it has gained the keyboard focus. - /// - SETFOCUS = 0x0007, - /// - /// The WM_KILLFOCUS message is sent to a window immediately before it loses the keyboard focus. - /// - KILLFOCUS = 0x0008, - /// - /// The WM_ENABLE message is sent when an application changes the enabled state of a window. It is sent to the window whose enabled state is changing. This message is sent before the EnableWindow function returns, but after the enabled state (WS_DISABLED style bit) of the window has changed. - /// - ENABLE = 0x000A, - /// - /// An application sends the WM_SETREDRAW message to a window to allow changes in that window to be redrawn or to prevent changes in that window from being redrawn. - /// - SETREDRAW = 0x000B, - /// - /// An application sends a WM_SETTEXT message to set the text of a window. - /// - SETTEXT = 0x000C, - /// - /// An application sends a WM_GETTEXT message to copy the text that corresponds to a window into a buffer provided by the caller. - /// - GETTEXT = 0x000D, - /// - /// An application sends a WM_GETTEXTLENGTH message to determine the length, in characters, of the text associated with a window. - /// - GETTEXTLENGTH = 0x000E, - /// - /// The WM_PAINT message is sent when the system or another application makes a request to paint a portion of an application's window. The message is sent when the UpdateWindow or RedrawWindow function is called, or by the DispatchMessage function when the application obtains a WM_PAINT message by using the GetMessage or PeekMessage function. - /// - PAINT = 0x000F, - /// - /// The WM_CLOSE message is sent as a signal that a window or an application should terminate. - /// - CLOSE = 0x0010, - /// - /// The WM_QUERYENDSESSION message is sent when the user chooses to end the session or when an application calls one of the system shutdown functions. If any application returns zero, the session is not ended. The system stops sending WM_QUERYENDSESSION messages as soon as one application returns zero. - /// After processing this message, the system sends the WM_ENDSESSION message with the wParam parameter set to the results of the WM_QUERYENDSESSION message. - /// - QUERYENDSESSION = 0x0011, - /// - /// The WM_QUERYOPEN message is sent to an icon when the user requests that the window be restored to its previous size and position. - /// - QUERYOPEN = 0x0013, - /// - /// The WM_ENDSESSION message is sent to an application after the system processes the results of the WM_QUERYENDSESSION message. The WM_ENDSESSION message informs the application whether the session is ending. - /// - ENDSESSION = 0x0016, - /// - /// The WM_QUIT message indicates a request to terminate an application and is generated when the application calls the PostQuitMessage function. It causes the GetMessage function to return zero. - /// - QUIT = 0x0012, - /// - /// The WM_ERASEBKGND message is sent when the window background must be erased (for example, when a window is resized). The message is sent to prepare an invalidated portion of a window for painting. - /// - ERASEBKGND = 0x0014, - /// - /// This message is sent to all top-level windows when a change is made to a system color setting. - /// - SYSCOLORCHANGE = 0x0015, - /// - /// The WM_SHOWWINDOW message is sent to a window when the window is about to be hidden or shown. - /// - SHOWWINDOW = 0x0018, - /// - /// An application sends the WM_WININICHANGE message to all top-level windows after making a change to the WIN.INI file. The SystemParametersInfo function sends this message after an application uses the function to change a setting in WIN.INI. - /// Note The WM_WININICHANGE message is provided only for compatibility with earlier versions of the system. Applications should use the WM_SETTINGCHANGE message. - /// - WININICHANGE = 0x001A, - /// - /// An application sends the WM_WININICHANGE message to all top-level windows after making a change to the WIN.INI file. The SystemParametersInfo function sends this message after an application uses the function to change a setting in WIN.INI. - /// Note The WM_WININICHANGE message is provided only for compatibility with earlier versions of the system. Applications should use the WM_SETTINGCHANGE message. - /// - SETTINGCHANGE = WININICHANGE, - /// - /// The WM_DEVMODECHANGE message is sent to all top-level windows whenever the user changes device-mode settings. - /// - DEVMODECHANGE = 0x001B, - /// - /// The WM_ACTIVATEAPP message is sent when a window belonging to a different application than the active window is about to be activated. The message is sent to the application whose window is being activated and to the application whose window is being deactivated. - /// - ACTIVATEAPP = 0x001C, - /// - /// An application sends the WM_FONTCHANGE message to all top-level windows in the system after changing the pool of font resources. - /// - FONTCHANGE = 0x001D, - /// - /// A message that is sent whenever there is a change in the system time. - /// - TIMECHANGE = 0x001E, - /// - /// The WM_CANCELMODE message is sent to cancel certain modes, such as mouse capture. For example, the system sends this message to the active window when a dialog box or message box is displayed. Certain functions also send this message explicitly to the specified window regardless of whether it is the active window. For example, the EnableWindow function sends this message when disabling the specified window. - /// - CANCELMODE = 0x001F, - /// - /// The WM_SETCURSOR message is sent to a window if the mouse causes the cursor to move within a window and mouse input is not captured. - /// - SETCURSOR = 0x0020, - /// - /// The WM_MOUSEACTIVATE message is sent when the cursor is in an inactive window and the user presses a mouse button. The parent window receives this message only if the child window passes it to the DefWindowProc function. - /// - MOUSEACTIVATE = 0x0021, - /// - /// The WM_CHILDACTIVATE message is sent to a child window when the user clicks the window's title bar or when the window is activated, moved, or sized. - /// - CHILDACTIVATE = 0x0022, - /// - /// The WM_QUEUESYNC message is sent by a computer-based training (CBT) application to separate user-input messages from other messages sent through the WH_JOURNALPLAYBACK Hook procedure. - /// - QUEUESYNC = 0x0023, - /// - /// The WM_GETMINMAXINFO message is sent to a window when the size or position of the window is about to change. An application can use this message to override the window's default maximized size and position, or its default minimum or maximum tracking size. - /// - GETMINMAXINFO = 0x0024, - /// - /// Windows NT 3.51 and earlier: The WM_PAINTICON message is sent to a minimized window when the icon is to be painted. This message is not sent by newer versions of Microsoft Windows, except in unusual circumstances explained in the Remarks. - /// - PAINTICON = 0x0026, - /// - /// Windows NT 3.51 and earlier: The WM_ICONERASEBKGND message is sent to a minimized window when the background of the icon must be filled before painting the icon. A window receives this message only if a class icon is defined for the window; otherwise, WM_ERASEBKGND is sent. This message is not sent by newer versions of Windows. - /// - ICONERASEBKGND = 0x0027, - /// - /// The WM_NEXTDLGCTL message is sent to a dialog box procedure to set the keyboard focus to a different control in the dialog box. - /// - NEXTDLGCTL = 0x0028, - /// - /// The WM_SPOOLERSTATUS message is sent from Print Manager whenever a job is added to or removed from the Print Manager queue. - /// - SPOOLERSTATUS = 0x002A, - /// - /// The WM_DRAWITEM message is sent to the parent window of an owner-drawn button, combo box, list box, or menu when a visual aspect of the button, combo box, list box, or menu has changed. - /// - DRAWITEM = 0x002B, - /// - /// The WM_MEASUREITEM message is sent to the owner window of a combo box, list box, list view control, or menu item when the control or menu is created. - /// - MEASUREITEM = 0x002C, - /// - /// Sent to the owner of a list box or combo box when the list box or combo box is destroyed or when items are removed by the LB_DELETESTRING, LB_RESETCONTENT, CB_DELETESTRING, or CB_RESETCONTENT message. The system sends a WM_DELETEITEM message for each deleted item. The system sends the WM_DELETEITEM message for any deleted list box or combo box item with nonzero item data. - /// - DELETEITEM = 0x002D, - /// - /// Sent by a list box with the LBS_WANTKEYBOARDINPUT style to its owner in response to a WM_KEYDOWN message. - /// - VKEYTOITEM = 0x002E, - /// - /// Sent by a list box with the LBS_WANTKEYBOARDINPUT style to its owner in response to a WM_CHAR message. - /// - CHARTOITEM = 0x002F, - /// - /// An application sends a WM_SETFONT message to specify the font that a control is to use when drawing text. - /// - SETFONT = 0x0030, - /// - /// An application sends a WM_GETFONT message to a control to retrieve the font with which the control is currently drawing its text. - /// - GETFONT = 0x0031, - /// - /// An application sends a WM_SETHOTKEY message to a window to associate a hot key with the window. When the user presses the hot key, the system activates the window. - /// - SETHOTKEY = 0x0032, - /// - /// An application sends a WM_GETHOTKEY message to determine the hot key associated with a window. - /// - GETHOTKEY = 0x0033, - /// - /// The WM_QUERYDRAGICON message is sent to a minimized (iconic) window. The window is about to be dragged by the user but does not have an icon defined for its class. An application can return a handle to an icon or cursor. The system displays this cursor or icon while the user drags the icon. - /// - QUERYDRAGICON = 0x0037, - /// - /// The system sends the WM_COMPAREITEM message to determine the relative position of a new item in the sorted list of an owner-drawn combo box or list box. Whenever the application adds a new item, the system sends this message to the owner of a combo box or list box created with the CBS_SORT or LBS_SORT style. - /// - COMPAREITEM = 0x0039, - /// - /// Active Accessibility sends the WM_GETOBJECT message to obtain information about an accessible object contained in a server application. - /// Applications never send this message directly. It is sent only by Active Accessibility in response to calls to AccessibleObjectFromPoint, AccessibleObjectFromEvent, or AccessibleObjectFromWindow. However, server applications handle this message. - /// - GETOBJECT = 0x003D, - /// - /// The WM_COMPACTING message is sent to all top-level windows when the system detects more than 12.5 percent of system time over a 30- to 60-second interval is being spent compacting memory. This indicates that system memory is low. - /// - COMPACTING = 0x0041, - /// - /// WM_COMMNOTIFY is Obsolete for Win32-Based Applications - /// - [Obsolete] - COMMNOTIFY = 0x0044, - /// - /// The WM_WINDOWPOSCHANGING message is sent to a window whose size, position, or place in the Z order is about to change as a result of a call to the SetWindowPos function or another window-management function. - /// - WINDOWPOSCHANGING = 0x0046, - /// - /// The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function. - /// - WINDOWPOSCHANGED = 0x0047, - /// - /// Notifies applications that the system, typically a battery-powered personal computer, is about to enter a suspended mode. - /// Use: POWERBROADCAST - /// - [Obsolete] - POWER = 0x0048, - /// - /// An application sends the WM_COPYDATA message to pass data to another application. - /// - COPYDATA = 0x004A, - /// - /// The WM_CANCELJOURNAL message is posted to an application when a user cancels the application's journaling activities. The message is posted with a NULL window handle. - /// - CANCELJOURNAL = 0x004B, - /// - /// Sent by a common control to its parent window when an event has occurred or the control requires some information. - /// - NOTIFY = 0x004E, - /// - /// The WM_INPUTLANGCHANGEREQUEST message is posted to the window with the focus when the user chooses a new input language, either with the hotkey (specified in the Keyboard control panel application) or from the indicator on the system taskbar. An application can accept the change by passing the message to the DefWindowProc function or reject the change (and prevent it from taking place) by returning immediately. - /// - INPUTLANGCHANGEREQUEST = 0x0050, - /// - /// The WM_INPUTLANGCHANGE message is sent to the topmost affected window after an application's input language has been changed. You should make any application-specific settings and pass the message to the DefWindowProc function, which passes the message to all first-level child windows. These child windows can pass the message to DefWindowProc to have it pass the message to their child windows, and so on. - /// - INPUTLANGCHANGE = 0x0051, - /// - /// Sent to an application that has initiated a training card with Microsoft Windows Help. The message informs the application when the user clicks an authorable button. An application initiates a training card by specifying the HELP_TCARD command in a call to the WinHelp function. - /// - TCARD = 0x0052, - /// - /// Indicates that the user pressed the F1 key. If a menu is active when F1 is pressed, WM_HELP is sent to the window associated with the menu; otherwise, WM_HELP is sent to the window that has the keyboard focus. If no window has the keyboard focus, WM_HELP is sent to the currently active window. - /// - HELP = 0x0053, - /// - /// The WM_USERCHANGED message is sent to all windows after the user has logged on or off. When the user logs on or off, the system updates the user-specific settings. The system sends this message immediately after updating the settings. - /// - USERCHANGED = 0x0054, - /// - /// Determines if a window accepts ANSI or Unicode structures in the WM_NOTIFY notification message. WM_NOTIFYFORMAT messages are sent from a common control to its parent window and from the parent window to the common control. - /// - NOTIFYFORMAT = 0x0055, - /// - /// The WM_CONTEXTMENU message notifies a window that the user clicked the right mouse button (right-clicked) in the window. - /// - CONTEXTMENU = 0x007B, - /// - /// The WM_STYLECHANGING message is sent to a window when the SetWindowLong function is about to change one or more of the window's styles. - /// - STYLECHANGING = 0x007C, - /// - /// The WM_STYLECHANGED message is sent to a window after the SetWindowLong function has changed one or more of the window's styles - /// - STYLECHANGED = 0x007D, - /// - /// The WM_DISPLAYCHANGE message is sent to all windows when the display resolution has changed. - /// - DISPLAYCHANGE = 0x007E, - /// - /// The WM_GETICON message is sent to a window to retrieve a handle to the large or small icon associated with a window. The system displays the large icon in the ALT+TAB dialog, and the small icon in the window caption. - /// - GETICON = 0x007F, - /// - /// An application sends the WM_SETICON message to associate a new large or small icon with a window. The system displays the large icon in the ALT+TAB dialog box, and the small icon in the window caption. - /// - SETICON = 0x0080, - /// - /// The WM_NCCREATE message is sent prior to the WM_CREATE message when a window is first created. - /// - NCCREATE = 0x0081, - /// - /// The WM_NCDESTROY message informs a window that its nonclient area is being destroyed. The DestroyWindow function sends the WM_NCDESTROY message to the window following the WM_DESTROY message. WM_DESTROY is used to free the allocated memory object associated with the window. - /// The WM_NCDESTROY message is sent after the child windows have been destroyed. In contrast, WM_DESTROY is sent before the child windows are destroyed. - /// - NCDESTROY = 0x0082, - /// - /// The WM_NCCALCSIZE message is sent when the size and position of a window's client area must be calculated. By processing this message, an application can control the content of the window's client area when the size or position of the window changes. - /// - NCCALCSIZE = 0x0083, - /// - /// The WM_NCHITTEST message is sent to a window when the cursor moves, or when a mouse button is pressed or released. If the mouse is not captured, the message is sent to the window beneath the cursor. Otherwise, the message is sent to the window that has captured the mouse. - /// - NCHITTEST = 0x0084, - /// - /// The WM_NCPAINT message is sent to a window when its frame must be painted. - /// - NCPAINT = 0x0085, - /// - /// The WM_NCACTIVATE message is sent to a window when its nonclient area needs to be changed to indicate an active or inactive state. - /// - NCACTIVATE = 0x0086, - /// - /// The WM_GETDLGCODE message is sent to the window procedure associated with a control. By default, the system handles all keyboard input to the control; the system interprets certain types of keyboard input as dialog box navigation keys. To override this default behavior, the control can respond to the WM_GETDLGCODE message to indicate the types of input it wants to process itself. - /// - GETDLGCODE = 0x0087, - /// - /// The WM_SYNCPAINT message is used to synchronize painting while avoiding linking independent GUI threads. - /// - SYNCPAINT = 0x0088, - /// - /// The WM_NCMOUSEMOVE message is posted to a window when the cursor is moved within the nonclient area of the window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCMOUSEMOVE = 0x00A0, - /// - /// The WM_NCLBUTTONDOWN message is posted when the user presses the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCLBUTTONDOWN = 0x00A1, - /// - /// The WM_NCLBUTTONUP message is posted when the user releases the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCLBUTTONUP = 0x00A2, - /// - /// The WM_NCLBUTTONDBLCLK message is posted when the user double-clicks the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCLBUTTONDBLCLK = 0x00A3, - /// - /// The WM_NCRBUTTONDOWN message is posted when the user presses the right mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCRBUTTONDOWN = 0x00A4, - /// - /// The WM_NCRBUTTONUP message is posted when the user releases the right mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCRBUTTONUP = 0x00A5, - /// - /// The WM_NCRBUTTONDBLCLK message is posted when the user double-clicks the right mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCRBUTTONDBLCLK = 0x00A6, - /// - /// The WM_NCMBUTTONDOWN message is posted when the user presses the middle mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCMBUTTONDOWN = 0x00A7, - /// - /// The WM_NCMBUTTONUP message is posted when the user releases the middle mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCMBUTTONUP = 0x00A8, - /// - /// The WM_NCMBUTTONDBLCLK message is posted when the user double-clicks the middle mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCMBUTTONDBLCLK = 0x00A9, - /// - /// The WM_NCXBUTTONDOWN message is posted when the user presses the first or second X button while the cursor is in the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCXBUTTONDOWN = 0x00AB, - /// - /// The WM_NCXBUTTONUP message is posted when the user releases the first or second X button while the cursor is in the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCXBUTTONUP = 0x00AC, - /// - /// The WM_NCXBUTTONDBLCLK message is posted when the user double-clicks the first or second X button while the cursor is in the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. - /// - NCXBUTTONDBLCLK = 0x00AD, - /// - /// The WM_INPUT_DEVICE_CHANGE message is sent to the window that registered to receive raw input. A window receives this message through its WindowProc function. - /// - INPUT_DEVICE_CHANGE = 0x00FE, - /// - /// The WM_INPUT message is sent to the window that is getting raw input. - /// - INPUT = 0x00FF, - /// - /// This message filters for keyboard messages. - /// - KEYFIRST = 0x0100, - /// - /// The WM_KEYDOWN message is posted to the window with the keyboard focus when a nonsystem key is pressed. A nonsystem key is a key that is pressed when the ALT key is not pressed. - /// - KEYDOWN = 0x0100, - /// - /// The WM_KEYUP message is posted to the window with the keyboard focus when a nonsystem key is released. A nonsystem key is a key that is pressed when the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus. - /// - KEYUP = 0x0101, - /// - /// The WM_CHAR message is posted to the window with the keyboard focus when a WM_KEYDOWN message is translated by the TranslateMessage function. The WM_CHAR message contains the character code of the key that was pressed. - /// - CHAR = 0x0102, - /// - /// The WM_DEADCHAR message is posted to the window with the keyboard focus when a WM_KEYUP message is translated by the TranslateMessage function. WM_DEADCHAR specifies a character code generated by a dead key. A dead key is a key that generates a character, such as the umlaut (double-dot), that is combined with another character to form a composite character. For example, the umlaut-O character (Ö) is generated by typing the dead key for the umlaut character, and then typing the O key. - /// - DEADCHAR = 0x0103, - /// - /// The WM_SYSKEYDOWN message is posted to the window with the keyboard focus when the user presses the F10 key (which activates the menu bar) or holds down the ALT key and then presses another key. It also occurs when no window currently has the keyboard focus; in this case, the WM_SYSKEYDOWN message is sent to the active window. The window that receives the message can distinguish between these two contexts by checking the context code in the lParam parameter. - /// - SYSKEYDOWN = 0x0104, - /// - /// The WM_SYSKEYUP message is posted to the window with the keyboard focus when the user releases a key that was pressed while the ALT key was held down. It also occurs when no window currently has the keyboard focus; in this case, the WM_SYSKEYUP message is sent to the active window. The window that receives the message can distinguish between these two contexts by checking the context code in the lParam parameter. - /// - SYSKEYUP = 0x0105, - /// - /// The WM_SYSCHAR message is posted to the window with the keyboard focus when a WM_SYSKEYDOWN message is translated by the TranslateMessage function. It specifies the character code of a system character key — that is, a character key that is pressed while the ALT key is down. - /// - SYSCHAR = 0x0106, - /// - /// The WM_SYSDEADCHAR message is sent to the window with the keyboard focus when a WM_SYSKEYDOWN message is translated by the TranslateMessage function. WM_SYSDEADCHAR specifies the character code of a system dead key — that is, a dead key that is pressed while holding down the ALT key. - /// - SYSDEADCHAR = 0x0107, - /// - /// The WM_UNICHAR message is posted to the window with the keyboard focus when a WM_KEYDOWN message is translated by the TranslateMessage function. The WM_UNICHAR message contains the character code of the key that was pressed. - /// The WM_UNICHAR message is equivalent to WM_CHAR, but it uses Unicode Transformation Format (UTF)-32, whereas WM_CHAR uses UTF-16. It is designed to send or post Unicode characters to ANSI windows and it can can handle Unicode Supplementary Plane characters. - /// - UNICHAR = 0x0109, - /// - /// This message filters for keyboard messages. - /// - KEYLAST = 0x0108, - /// - /// Sent immediately before the IME generates the composition string as a result of a keystroke. A window receives this message through its WindowProc function. - /// - IME_STARTCOMPOSITION = 0x010D, - /// - /// Sent to an application when the IME ends composition. A window receives this message through its WindowProc function. - /// - IME_ENDCOMPOSITION = 0x010E, - /// - /// Sent to an application when the IME changes composition status as a result of a keystroke. A window receives this message through its WindowProc function. - /// - IME_COMPOSITION = 0x010F, - IME_KEYLAST = 0x010F, - /// - /// The WM_INITDIALOG message is sent to the dialog box procedure immediately before a dialog box is displayed. Dialog box procedures typically use this message to initialize controls and carry out any other initialization tasks that affect the appearance of the dialog box. - /// - INITDIALOG = 0x0110, - /// - /// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated. - /// - COMMAND = 0x0111, - /// - /// A window receives this message when the user chooses a command from the Window menu, clicks the maximize button, minimize button, restore button, close button, or moves the form. You can stop the form from moving by filtering this out. - /// - SYSCOMMAND = 0x0112, - /// - /// The WM_TIMER message is posted to the installing thread's message queue when a timer expires. The message is posted by the GetMessage or PeekMessage function. - /// - TIMER = 0x0113, - /// - /// The WM_HSCROLL message is sent to a window when a scroll event occurs in the window's standard horizontal scroll bar. This message is also sent to the owner of a horizontal scroll bar control when a scroll event occurs in the control. - /// - HSCROLL = 0x0114, - /// - /// The WM_VSCROLL message is sent to a window when a scroll event occurs in the window's standard vertical scroll bar. This message is also sent to the owner of a vertical scroll bar control when a scroll event occurs in the control. - /// - VSCROLL = 0x0115, - /// - /// The WM_INITMENU message is sent when a menu is about to become active. It occurs when the user clicks an item on the menu bar or presses a menu key. This allows the application to modify the menu before it is displayed. - /// - INITMENU = 0x0116, - /// - /// The WM_INITMENUPOPUP message is sent when a drop-down menu or submenu is about to become active. This allows an application to modify the menu before it is displayed, without changing the entire menu. - /// - INITMENUPOPUP = 0x0117, - /// - /// The WM_MENUSELECT message is sent to a menu's owner window when the user selects a menu item. - /// - MENUSELECT = 0x011F, - /// - /// The WM_MENUCHAR message is sent when a menu is active and the user presses a key that does not correspond to any mnemonic or accelerator key. This message is sent to the window that owns the menu. - /// - MENUCHAR = 0x0120, - /// - /// The WM_ENTERIDLE message is sent to the owner window of a modal dialog box or menu that is entering an idle state. A modal dialog box or menu enters an idle state when no messages are waiting in its queue after it has processed one or more previous messages. - /// - ENTERIDLE = 0x0121, - /// - /// The WM_MENURBUTTONUP message is sent when the user releases the right mouse button while the cursor is on a menu item. - /// - MENURBUTTONUP = 0x0122, - /// - /// The WM_MENUDRAG message is sent to the owner of a drag-and-drop menu when the user drags a menu item. - /// - MENUDRAG = 0x0123, - /// - /// The WM_MENUGETOBJECT message is sent to the owner of a drag-and-drop menu when the mouse cursor enters a menu item or moves from the center of the item to the top or bottom of the item. - /// - MENUGETOBJECT = 0x0124, - /// - /// The WM_UNINITMENUPOPUP message is sent when a drop-down menu or submenu has been destroyed. - /// - UNINITMENUPOPUP = 0x0125, - /// - /// The WM_MENUCOMMAND message is sent when the user makes a selection from a menu. - /// - MENUCOMMAND = 0x0126, - /// - /// An application sends the WM_CHANGEUISTATE message to indicate that the user interface (UI) state should be changed. - /// - CHANGEUISTATE = 0x0127, - /// - /// An application sends the WM_UPDATEUISTATE message to change the user interface (UI) state for the specified window and all its child windows. - /// - UPDATEUISTATE = 0x0128, - /// - /// An application sends the WM_QUERYUISTATE message to retrieve the user interface (UI) state for a window. - /// - QUERYUISTATE = 0x0129, - /// - /// The WM_CTLCOLORMSGBOX message is sent to the owner window of a message box before Windows draws the message box. By responding to this message, the owner window can set the text and background colors of the message box by using the given display device context handle. - /// - CTLCOLORMSGBOX = 0x0132, - /// - /// An edit control that is not read-only or disabled sends the WM_CTLCOLOREDIT message to its parent window when the control is about to be drawn. By responding to this message, the parent window can use the specified device context handle to set the text and background colors of the edit control. - /// - CTLCOLOREDIT = 0x0133, - /// - /// Sent to the parent window of a list box before the system draws the list box. By responding to this message, the parent window can set the text and background colors of the list box by using the specified display device context handle. - /// - CTLCOLORLISTBOX = 0x0134, - /// - /// The WM_CTLCOLORBTN message is sent to the parent window of a button before drawing the button. The parent window can change the button's text and background colors. However, only owner-drawn buttons respond to the parent window processing this message. - /// - CTLCOLORBTN = 0x0135, - /// - /// The WM_CTLCOLORDLG message is sent to a dialog box before the system draws the dialog box. By responding to this message, the dialog box can set its text and background colors using the specified display device context handle. - /// - CTLCOLORDLG = 0x0136, - /// - /// The WM_CTLCOLORSCROLLBAR message is sent to the parent window of a scroll bar control when the control is about to be drawn. By responding to this message, the parent window can use the display context handle to set the background color of the scroll bar control. - /// - CTLCOLORSCROLLBAR = 0x0137, - /// - /// A static control, or an edit control that is read-only or disabled, sends the WM_CTLCOLORSTATIC message to its parent window when the control is about to be drawn. By responding to this message, the parent window can use the specified device context handle to set the text and background colors of the static control. - /// - CTLCOLORSTATIC = 0x0138, - /// - /// Use WM_MOUSEFIRST to specify the first mouse message. Use the PeekMessage() Function. - /// - MOUSEFIRST = 0x0200, - /// - /// The WM_MOUSEMOVE message is posted to a window when the cursor moves. If the mouse is not captured, the message is posted to the window that contains the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - MOUSEMOVE = 0x0200, - /// - /// The WM_LBUTTONDOWN message is posted when the user presses the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - LBUTTONDOWN = 0x0201, - /// - /// The WM_LBUTTONUP message is posted when the user releases the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - LBUTTONUP = 0x0202, - /// - /// The WM_LBUTTONDBLCLK message is posted when the user double-clicks the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - LBUTTONDBLCLK = 0x0203, - /// - /// The WM_RBUTTONDOWN message is posted when the user presses the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - RBUTTONDOWN = 0x0204, - /// - /// The WM_RBUTTONUP message is posted when the user releases the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - RBUTTONUP = 0x0205, - /// - /// The WM_RBUTTONDBLCLK message is posted when the user double-clicks the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - RBUTTONDBLCLK = 0x0206, - /// - /// The WM_MBUTTONDOWN message is posted when the user presses the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - MBUTTONDOWN = 0x0207, - /// - /// The WM_MBUTTONUP message is posted when the user releases the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - MBUTTONUP = 0x0208, - /// - /// The WM_MBUTTONDBLCLK message is posted when the user double-clicks the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - MBUTTONDBLCLK = 0x0209, - /// - /// The WM_MOUSEWHEEL message is sent to the focus window when the mouse wheel is rotated. The DefWindowProc function propagates the message to the window's parent. There should be no internal forwarding of the message, since DefWindowProc propagates it up the parent chain until it finds a window that processes it. - /// - MOUSEWHEEL = 0x020A, - /// - /// The WM_XBUTTONDOWN message is posted when the user presses the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - XBUTTONDOWN = 0x020B, - /// - /// The WM_XBUTTONUP message is posted when the user releases the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - XBUTTONUP = 0x020C, - /// - /// The WM_XBUTTONDBLCLK message is posted when the user double-clicks the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. - /// - XBUTTONDBLCLK = 0x020D, - /// - /// The WM_MOUSEHWHEEL message is sent to the focus window when the mouse's horizontal scroll wheel is tilted or rotated. The DefWindowProc function propagates the message to the window's parent. There should be no internal forwarding of the message, since DefWindowProc propagates it up the parent chain until it finds a window that processes it. - /// - MOUSEHWHEEL = 0x020E, - /// - /// Use WM_MOUSELAST to specify the last mouse message. Used with PeekMessage() Function. - /// - MOUSELAST = 0x020E, - /// - /// The WM_PARENTNOTIFY message is sent to the parent of a child window when the child window is created or destroyed, or when the user clicks a mouse button while the cursor is over the child window. When the child window is being created, the system sends WM_PARENTNOTIFY just before the CreateWindow or CreateWindowEx function that creates the window returns. When the child window is being destroyed, the system sends the message before any processing to destroy the window takes place. - /// - PARENTNOTIFY = 0x0210, - /// - /// The WM_ENTERMENULOOP message informs an application's main window procedure that a menu modal loop has been entered. - /// - ENTERMENULOOP = 0x0211, - /// - /// The WM_EXITMENULOOP message informs an application's main window procedure that a menu modal loop has been exited. - /// - EXITMENULOOP = 0x0212, - /// - /// The WM_NEXTMENU message is sent to an application when the right or left arrow key is used to switch between the menu bar and the system menu. - /// - NEXTMENU = 0x0213, - /// - /// The WM_SIZING message is sent to a window that the user is resizing. By processing this message, an application can monitor the size and position of the drag rectangle and, if needed, change its size or position. - /// - SIZING = 0x0214, - /// - /// The WM_CAPTURECHANGED message is sent to the window that is losing the mouse capture. - /// - CAPTURECHANGED = 0x0215, - /// - /// The WM_MOVING message is sent to a window that the user is moving. By processing this message, an application can monitor the position of the drag rectangle and, if needed, change its position. - /// - MOVING = 0x0216, - /// - /// Notifies applications that a power-management event has occurred. - /// - POWERBROADCAST = 0x0218, - /// - /// Notifies an application of a change to the hardware configuration of a device or the computer. - /// - DEVICECHANGE = 0x0219, - /// - /// An application sends the WM_MDICREATE message to a multiple-document interface (MDI) client window to create an MDI child window. - /// - MDICREATE = 0x0220, - /// - /// An application sends the WM_MDIDESTROY message to a multiple-document interface (MDI) client window to close an MDI child window. - /// - MDIDESTROY = 0x0221, - /// - /// An application sends the WM_MDIACTIVATE message to a multiple-document interface (MDI) client window to instruct the client window to activate a different MDI child window. - /// - MDIACTIVATE = 0x0222, - /// - /// An application sends the WM_MDIRESTORE message to a multiple-document interface (MDI) client window to restore an MDI child window from maximized or minimized size. - /// - MDIRESTORE = 0x0223, - /// - /// An application sends the WM_MDINEXT message to a multiple-document interface (MDI) client window to activate the next or previous child window. - /// - MDINEXT = 0x0224, - /// - /// An application sends the WM_MDIMAXIMIZE message to a multiple-document interface (MDI) client window to maximize an MDI child window. The system resizes the child window to make its client area fill the client window. The system places the child window's window menu icon in the rightmost position of the frame window's menu bar, and places the child window's restore icon in the leftmost position. The system also appends the title bar text of the child window to that of the frame window. - /// - MDIMAXIMIZE = 0x0225, - /// - /// An application sends the WM_MDITILE message to a multiple-document interface (MDI) client window to arrange all of its MDI child windows in a tile format. - /// - MDITILE = 0x0226, - /// - /// An application sends the WM_MDICASCADE message to a multiple-document interface (MDI) client window to arrange all its child windows in a cascade format. - /// - MDICASCADE = 0x0227, - /// - /// An application sends the WM_MDIICONARRANGE message to a multiple-document interface (MDI) client window to arrange all minimized MDI child windows. It does not affect child windows that are not minimized. - /// - MDIICONARRANGE = 0x0228, - /// - /// An application sends the WM_MDIGETACTIVE message to a multiple-document interface (MDI) client window to retrieve the handle to the active MDI child window. - /// - MDIGETACTIVE = 0x0229, - /// - /// An application sends the WM_MDISETMENU message to a multiple-document interface (MDI) client window to replace the entire menu of an MDI frame window, to replace the window menu of the frame window, or both. - /// - MDISETMENU = 0x0230, - /// - /// The WM_ENTERSIZEMOVE message is sent one time to a window after it enters the moving or sizing modal loop. The window enters the moving or sizing modal loop when the user clicks the window's title bar or sizing border, or when the window passes the WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the message specifies the SC_MOVE or SC_SIZE value. The operation is complete when DefWindowProc returns. - /// The system sends the WM_ENTERSIZEMOVE message regardless of whether the dragging of full windows is enabled. - /// - ENTERSIZEMOVE = 0x0231, - /// - /// The WM_EXITSIZEMOVE message is sent one time to a window, after it has exited the moving or sizing modal loop. The window enters the moving or sizing modal loop when the user clicks the window's title bar or sizing border, or when the window passes the WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the message specifies the SC_MOVE or SC_SIZE value. The operation is complete when DefWindowProc returns. - /// - EXITSIZEMOVE = 0x0232, - /// - /// Sent when the user drops a file on the window of an application that has registered itself as a recipient of dropped files. - /// - DROPFILES = 0x0233, - /// - /// An application sends the WM_MDIREFRESHMENU message to a multiple-document interface (MDI) client window to refresh the window menu of the MDI frame window. - /// - MDIREFRESHMENU = 0x0234, - /// - /// Sent to an application when a window is activated. A window receives this message through its WindowProc function. - /// - IME_SETCONTEXT = 0x0281, - /// - /// Sent to an application to notify it of changes to the IME window. A window receives this message through its WindowProc function. - /// - IME_NOTIFY = 0x0282, - /// - /// Sent by an application to direct the IME window to carry out the requested command. The application uses this message to control the IME window that it has created. To send this message, the application calls the SendMessage function with the following parameters. - /// - IME_CONTROL = 0x0283, - /// - /// Sent to an application when the IME window finds no space to extend the area for the composition window. A window receives this message through its WindowProc function. - /// - IME_COMPOSITIONFULL = 0x0284, - /// - /// Sent to an application when the operating system is about to change the current IME. A window receives this message through its WindowProc function. - /// - IME_SELECT = 0x0285, - /// - /// Sent to an application when the IME gets a character of the conversion result. A window receives this message through its WindowProc function. - /// - IME_CHAR = 0x0286, - /// - /// Sent to an application to provide commands and request information. A window receives this message through its WindowProc function. - /// - IME_REQUEST = 0x0288, - /// - /// Sent to an application by the IME to notify the application of a key press and to keep message order. A window receives this message through its WindowProc function. - /// - IME_KEYDOWN = 0x0290, - /// - /// Sent to an application by the IME to notify the application of a key release and to keep message order. A window receives this message through its WindowProc function. - /// - IME_KEYUP = 0x0291, - /// - /// The WM_MOUSEHOVER message is posted to a window when the cursor hovers over the client area of the window for the period of time specified in a prior call to TrackMouseEvent. - /// - MOUSEHOVER = 0x02A1, - /// - /// The WM_MOUSELEAVE message is posted to a window when the cursor leaves the client area of the window specified in a prior call to TrackMouseEvent. - /// - MOUSELEAVE = 0x02A3, - /// - /// The WM_NCMOUSEHOVER message is posted to a window when the cursor hovers over the nonclient area of the window for the period of time specified in a prior call to TrackMouseEvent. - /// - NCMOUSEHOVER = 0x02A0, - /// - /// The WM_NCMOUSELEAVE message is posted to a window when the cursor leaves the nonclient area of the window specified in a prior call to TrackMouseEvent. - /// - NCMOUSELEAVE = 0x02A2, - /// - /// The WM_WTSSESSION_CHANGE message notifies applications of changes in session state. - /// - WTSSESSION_CHANGE = 0x02B1, - TABLET_FIRST = 0x02c0, - TABLET_LAST = 0x02df, - /// - /// An application sends a WM_CUT message to an edit control or combo box to delete (cut) the current selection, if any, in the edit control and copy the deleted text to the clipboard in CF_TEXT format. - /// - CUT = 0x0300, - /// - /// An application sends the WM_COPY message to an edit control or combo box to copy the current selection to the clipboard in CF_TEXT format. - /// - COPY = 0x0301, - /// - /// An application sends a WM_PASTE message to an edit control or combo box to copy the current content of the clipboard to the edit control at the current caret position. Data is inserted only if the clipboard contains data in CF_TEXT format. - /// - PASTE = 0x0302, - /// - /// An application sends a WM_CLEAR message to an edit control or combo box to delete (clear) the current selection, if any, from the edit control. - /// - CLEAR = 0x0303, - /// - /// An application sends a WM_UNDO message to an edit control to undo the last operation. When this message is sent to an edit control, the previously deleted text is restored or the previously added text is deleted. - /// - UNDO = 0x0304, - /// - /// The WM_RENDERFORMAT message is sent to the clipboard owner if it has delayed rendering a specific clipboard format and if an application has requested data in that format. The clipboard owner must render data in the specified format and place it on the clipboard by calling the SetClipboardData function. - /// - RENDERFORMAT = 0x0305, - /// - /// The WM_RENDERALLFORMATS message is sent to the clipboard owner before it is destroyed, if the clipboard owner has delayed rendering one or more clipboard formats. For the content of the clipboard to remain available to other applications, the clipboard owner must render data in all the formats it is capable of generating, and place the data on the clipboard by calling the SetClipboardData function. - /// - RENDERALLFORMATS = 0x0306, - /// - /// The WM_DESTROYCLIPBOARD message is sent to the clipboard owner when a call to the EmptyClipboard function empties the clipboard. - /// - DESTROYCLIPBOARD = 0x0307, - /// - /// The WM_DRAWCLIPBOARD message is sent to the first window in the clipboard viewer chain when the content of the clipboard changes. This enables a clipboard viewer window to display the new content of the clipboard. - /// - DRAWCLIPBOARD = 0x0308, - /// - /// The WM_PAINTCLIPBOARD message is sent to the clipboard owner by a clipboard viewer window when the clipboard contains data in the CF_OWNERDISPLAY format and the clipboard viewer's client area needs repainting. - /// - PAINTCLIPBOARD = 0x0309, - /// - /// The WM_VSCROLLCLIPBOARD message is sent to the clipboard owner by a clipboard viewer window when the clipboard contains data in the CF_OWNERDISPLAY format and an event occurs in the clipboard viewer's vertical scroll bar. The owner should scroll the clipboard image and update the scroll bar values. - /// - VSCROLLCLIPBOARD = 0x030A, - /// - /// The WM_SIZECLIPBOARD message is sent to the clipboard owner by a clipboard viewer window when the clipboard contains data in the CF_OWNERDISPLAY format and the clipboard viewer's client area has changed size. - /// - SIZECLIPBOARD = 0x030B, - /// - /// The WM_ASKCBFORMATNAME message is sent to the clipboard owner by a clipboard viewer window to request the name of a CF_OWNERDISPLAY clipboard format. - /// - ASKCBFORMATNAME = 0x030C, - /// - /// The WM_CHANGECBCHAIN message is sent to the first window in the clipboard viewer chain when a window is being removed from the chain. - /// - CHANGECBCHAIN = 0x030D, - /// - /// The WM_HSCROLLCLIPBOARD message is sent to the clipboard owner by a clipboard viewer window. This occurs when the clipboard contains data in the CF_OWNERDISPLAY format and an event occurs in the clipboard viewer's horizontal scroll bar. The owner should scroll the clipboard image and update the scroll bar values. - /// - HSCROLLCLIPBOARD = 0x030E, - /// - /// This message informs a window that it is about to receive the keyboard focus, giving the window the opportunity to realize its logical palette when it receives the focus. - /// - QUERYNEWPALETTE = 0x030F, - /// - /// The WM_PALETTEISCHANGING message informs applications that an application is going to realize its logical palette. - /// - PALETTEISCHANGING = 0x0310, - /// - /// This message is sent by the OS to all top-level and overlapped windows after the window with the keyboard focus realizes its logical palette. - /// This message enables windows that do not have the keyboard focus to realize their logical palettes and update their client areas. - /// - PALETTECHANGED = 0x0311, - /// - /// The WM_HOTKEY message is posted when the user presses a hot key registered by the RegisterHotKey function. The message is placed at the top of the message queue associated with the thread that registered the hot key. - /// - HOTKEY = 0x0312, - /// - /// The WM_PRINT message is sent to a window to request that it draw itself in the specified device context, most commonly in a printer device context. - /// - PRINT = 0x0317, - /// - /// The WM_PRINTCLIENT message is sent to a window to request that it draw its client area in the specified device context, most commonly in a printer device context. - /// - PRINTCLIENT = 0x0318, - /// - /// The WM_APPCOMMAND message notifies a window that the user generated an application command event, for example, by clicking an application command button using the mouse or typing an application command key on the keyboard. - /// - APPCOMMAND = 0x0319, - /// - /// The WM_THEMECHANGED message is broadcast to every window following a theme change event. Examples of theme change events are the activation of a theme, the deactivation of a theme, or a transition from one theme to another. - /// - THEMECHANGED = 0x031A, - /// - /// Sent when the contents of the clipboard have changed. - /// - CLIPBOARDUPDATE = 0x031D, - /// - /// The system will send a window the WM_DWMCOMPOSITIONCHANGED message to indicate that the availability of desktop composition has changed. - /// - DWMCOMPOSITIONCHANGED = 0x031E, - /// - /// WM_DWMNCRENDERINGCHANGED is called when the non-client area rendering status of a window has changed. Only windows that have set the flag DWM_BLURBEHIND.fTransitionOnMaximized to true will get this message. - /// - DWMNCRENDERINGCHANGED = 0x031F, - /// - /// Sent to all top-level windows when the colorization color has changed. - /// - DWMCOLORIZATIONCOLORCHANGED = 0x0320, - /// - /// WM_DWMWINDOWMAXIMIZEDCHANGE will let you know when a DWM composed window is maximized. You also have to register for this message as well. You'd have other windowd go opaque when this message is sent. - /// - DWMWINDOWMAXIMIZEDCHANGE = 0x0321, - /// - /// Sent to request extended title bar information. A window receives this message through its WindowProc function. - /// - GETTITLEBARINFOEX = 0x033F, - HANDHELDFIRST = 0x0358, - HANDHELDLAST = 0x035F, - AFXFIRST = 0x0360, - AFXLAST = 0x037F, - PENWINFIRST = 0x0380, - PENWINLAST = 0x038F, - /// - /// The WM_APP constant is used by applications to help define private messages, usually of the form WM_APP+X, where X is an integer value. - /// - APP = 0x8000, - /// - /// The WM_USER constant is used by applications to help define private messages for use by private window classes, usually of the form WM_USER+X, where X is an integer value. - /// - USER = 0x0400, - - /// - /// An application sends the WM_CPL_LAUNCH message to Windows Control Panel to request that a Control Panel application be started. - /// - CPL_LAUNCH = USER + 0x1000, - /// - /// The WM_CPL_LAUNCHED message is sent when a Control Panel application, started by the WM_CPL_LAUNCH message, has closed. The WM_CPL_LAUNCHED message is sent to the window identified by the wParam parameter of the WM_CPL_LAUNCH message that started the application. - /// - CPL_LAUNCHED = USER + 0x1001, - /// - /// WM_SYSTIMER is a well-known yet still undocumented message. Windows uses WM_SYSTIMER for internal actions like scrolling. - /// - SYSTIMER = 0x118, - - /// - /// The accessibility state has changed. - /// - HSHELL_ACCESSIBILITYSTATE = 11, - /// - /// The shell should activate its main window. - /// - HSHELL_ACTIVATESHELLWINDOW = 3, - /// - /// The user completed an input event (for example, pressed an application command button on the mouse or an application command key on the keyboard), and the application did not handle the WM_APPCOMMAND message generated by that input. - /// If the Shell procedure handles the WM_COMMAND message, it should not call CallNextHookEx. See the Return Value section for more information. - /// - HSHELL_APPCOMMAND = 12, - /// - /// A window is being minimized or maximized. The system needs the coordinates of the minimized rectangle for the window. - /// - HSHELL_GETMINRECT = 5, - /// - /// Keyboard language was changed or a new keyboard layout was loaded. - /// - HSHELL_LANGUAGE = 8, - /// - /// The title of a window in the task bar has been redrawn. - /// - HSHELL_REDRAW = 6, - /// - /// The user has selected the task list. A shell application that provides a task list should return TRUE to prevent Windows from starting its task list. - /// - HSHELL_TASKMAN = 7, - /// - /// A top-level, unowned window has been created. The window exists when the system calls this hook. - /// - HSHELL_WINDOWCREATED = 1, - /// - /// A top-level, unowned window is about to be destroyed. The window still exists when the system calls this hook. - /// - HSHELL_WINDOWDESTROYED = 2, - /// - /// The activation has changed to a different top-level, unowned window. - /// - HSHELL_WINDOWACTIVATED = 4, - /// - /// A top-level window is being replaced. The window exists when the system calls this hook. - /// - HSHELL_WINDOWREPLACED = 13 - } -} diff --git a/Dependencies/MelonStartScreen/mgGif/Decoder.cs b/Dependencies/MelonStartScreen/mgGif/Decoder.cs deleted file mode 100644 index a9b34d624..000000000 --- a/Dependencies/MelonStartScreen/mgGif/Decoder.cs +++ /dev/null @@ -1,628 +0,0 @@ -using System; -using System.Text; -using MelonUnityEngine; -using MelonLoader; - -namespace mgGif -{ - internal class Decoder : IDisposable - { - public string Version; - public ushort Width; - public ushort Height; - public Color32 BackgroundColour; - - //------------------------------------------------------------------------------ - // GIF format enums - - private enum ImageFlag - { - Interlaced = 0x40, - ColourTable = 0x80, - TableSizeMask = 0x07, - BitDepthMask = 0x70, - } - - private enum Block - { - Image = 0x2C, - Extension = 0x21, - End = 0x3B - } - - private enum Extension - { - GraphicControl = 0xF9, - Comments = 0xFE, - PlainText = 0x01, - ApplicationData = 0xFF - } - - private enum Disposal - { - None = 0x00, - DoNotDispose = 0x04, - RestoreBackground = 0x08, - ReturnToPrevious = 0x0C - } - - private enum ControlFlags - { - HasTransparency = 0x01, - DisposalMask = 0x0C - } - - //------------------------------------------------------------------------------ - - private const uint NoCode = 0xFFFF; - private const ushort NoTransparency = 0xFFFF; - - // input stream to decode - private byte[] Input; - - private int D; - - // colour table - private Color32[] GlobalColourTable; - - private Color32[] LocalColourTable; - private Color32[] ActiveColourTable; - private ushort TransparentIndex; - - // current image - private Image Image = new Image(); - - private ushort ImageLeft; - private ushort ImageTop; - private ushort ImageWidth; - private ushort ImageHeight; - - private Color32[] Output; - private Color32[] PreviousImage; - - private readonly int[] Pow2 = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 }; - - //------------------------------------------------------------------------------ - // ctor - - public Decoder(byte[] data) : this() - => Load(data); - - public Decoder Load(byte[] data) - { - Input = data; - D = 0; - - GlobalColourTable = new Color32[256]; - LocalColourTable = new Color32[256]; - TransparentIndex = NoTransparency; - Output = null; - PreviousImage = null; - - Image.Delay = 0; - - return this; - } - - //------------------------------------------------------------------------------ - // reading data utility functions - - private byte ReadByte() => Input[D++]; - - private ushort ReadUInt16() => (ushort)(Input[D++] | Input[D++] << 8); - - //------------------------------------------------------------------------------ - - private void ReadHeader() - { - if (Input == null || Input.Length <= 12) - throw new Exception("Invalid data"); - - // signature - - Version = Encoding.ASCII.GetString(Input, 0, 6); - D = 6; - - if (Version != "GIF87a" && Version != "GIF89a") - { - throw new Exception("Unsupported GIF version"); - } - - // read header - - Width = ReadUInt16(); - Height = ReadUInt16(); - - Image.Width = Width; - Image.Height = Height; - - var flags = (ImageFlag)ReadByte(); - var bgIndex = ReadByte(); // background colour - - ReadByte(); // aspect ratio - - if (flags.HasFlag(ImageFlag.ColourTable)) - ReadColourTable(GlobalColourTable, flags); - - BackgroundColour = GlobalColourTable[bgIndex]; - } - - //------------------------------------------------------------------------------ - - public Image NextImage() - { - // if at start of data, read header - - if (D == 0) - ReadHeader(); - - // read blocks until we find an image block - - while (true) - { - var block = (Block)ReadByte(); - - switch (block) - { - case Block.Image: - { - // return the image if we got one - - var img = ReadImageBlock(); - - if (img != null) - return img; - } - break; - - case Block.Extension: - { - var ext = (Extension)ReadByte(); - - if (ext == Extension.GraphicControl) - ReadControlBlock(); - else - SkipBlocks(); - } - break; - - case Block.End: - { - // end block - stop! - return null; - } - - default: - { - throw new Exception("Unexpected block type"); - } - } - } - } - - //------------------------------------------------------------------------------ - - private Color32[] ReadColourTable(Color32[] colourTable, ImageFlag flags) - { - var tableSize = Pow2[(int)(flags & ImageFlag.TableSizeMask) + 1]; - - for (var i = 0; i < tableSize; i++) - { - colourTable[i] = new Color32( - Input[D++], - Input[D++], - Input[D++], - 0xFF - ); - } - - return colourTable; - } - - //------------------------------------------------------------------------------ - - private void SkipBlocks() - { - var blockSize = Input[D++]; - - while (blockSize != 0x00) - { - D += blockSize; - blockSize = Input[D++]; - } - } - - //------------------------------------------------------------------------------ - - private void ReadControlBlock() - { - // read block - - ReadByte(); // block size (0x04) - var flags = (ControlFlags)ReadByte(); // flags - Image.Delay = ReadUInt16() * 10; // delay (1/100th -> milliseconds) - var transparentColour = ReadByte(); // transparent colour - ReadByte(); // terminator (0x00) - - // has transparent colour? - - if (flags.HasFlag(ControlFlags.HasTransparency)) - TransparentIndex = transparentColour; - else - TransparentIndex = NoTransparency; - - // dispose of current image - - switch ((Disposal)(flags & ControlFlags.DisposalMask)) - { - default: - case Disposal.None: - case Disposal.DoNotDispose: - // remember current image in case we need to "return to previous" - PreviousImage = Output; - break; - - case Disposal.RestoreBackground: - // empty image - don't track - Output = new Color32[Width * Height]; - break; - - case Disposal.ReturnToPrevious: - - // return to previous image - - Output = new Color32[Width * Height]; - - if (PreviousImage != null) - Array.Copy(PreviousImage, Output, Output.Length); - - break; - } - } - - //------------------------------------------------------------------------------ - - private Image ReadImageBlock() - { - // read image block header - - ImageLeft = ReadUInt16(); - ImageTop = ReadUInt16(); - ImageWidth = ReadUInt16(); - ImageHeight = ReadUInt16(); - var flags = (ImageFlag)ReadByte(); - - // bad image if we don't have any dimensions - - if (ImageWidth == 0 || ImageHeight == 0) - return null; - - // read colour table - - if (flags.HasFlag(ImageFlag.ColourTable)) - ActiveColourTable = ReadColourTable(LocalColourTable, flags); - else - ActiveColourTable = GlobalColourTable; - - if (Output == null) - { - Output = new Color32[Width * Height]; - PreviousImage = Output; - } - - // read image data - - DecompressLZW(); - - // deinterlace - - if (flags.HasFlag(ImageFlag.Interlaced)) - Deinterlace(); - - // return image - - Image.RawImage = Output; - return Image; - } - - //------------------------------------------------------------------------------ - // decode interlaced images - - private void Deinterlace() - { - var numRows = Output.Length / Width; - var writePos = Output.Length - Width; // NB: work backwards due to Y-coord flip - var input = Output; - - Output = new Color32[Output.Length]; - - for (var row = 0; row < numRows; row++) - { - int copyRow; - - // every 8th row starting at 0 - if (row % 8 == 0) - { - copyRow = row / 8; - } - // every 8th row starting at 4 - else if ((row + 4) % 8 == 0) - { - var o = numRows / 8; - copyRow = o + (row - 4) / 8; - } - // every 4th row starting at 2 - else if ((row + 2) % 4 == 0) - { - var o = numRows / 4; - copyRow = o + (row - 2) / 4; - } - // every 2nd row starting at 1 - else // if( ( r + 1 ) % 2 == 0 ) - { - var o = numRows / 2; - copyRow = o + (row - 1) / 2; - } - - Array.Copy(input, (numRows - copyRow - 1) * Width, Output, writePos, Width); - - writePos -= Width; - } - } - - //------------------------------------------------------------------------------ - - // dispose isn't needed for the safe implementation but keep here for interface parity - - public Decoder() { } - - public void Dispose() { Dispose(true); } - - protected virtual void Dispose(bool disposing) { } - - private int[] Indices = new int[4096]; - private ushort[] Codes = new ushort[128 * 1024]; - private uint[] CurBlock = new uint[64]; - - private void DecompressLZW() - { - // output write position - - int row = (Height - ImageTop - 1) * Width; // reverse rows for unity texture coords - int col = ImageLeft; - int rightEdge = ImageLeft + ImageWidth; - - // setup codes - - int minimumCodeSize = Input[D++]; - - if (minimumCodeSize > 11) - minimumCodeSize = 11; - - var codeSize = minimumCodeSize + 1; - var nextSize = Pow2[codeSize]; - var maximumCodeSize = Pow2[minimumCodeSize]; - var clearCode = maximumCodeSize; - var endCode = maximumCodeSize + 1; - - // initialise buffers - - var codesEnd = 0; - var numCodes = maximumCodeSize + 2; - - for (ushort i = 0; i < numCodes; i++) - { - Indices[i] = codesEnd; - Codes[codesEnd++] = 1; // length - Codes[codesEnd++] = i; // code - } - - // LZW decode loop - - uint previousCode = NoCode; // last code processed - uint mask = (uint)(nextSize - 1); // mask out code bits - uint shiftRegister = 0; // shift register holds the bytes coming in from the input stream, we shift down by the number of bits - - int bitsAvailable = 0; // number of bits available to read in the shift register - int bytesAvailable = 0; // number of bytes left in current block - - int blockPos = 0; - - while (true) - { - // get next code - - uint curCode = shiftRegister & mask; - - if (bitsAvailable >= codeSize) - { - bitsAvailable -= codeSize; - shiftRegister >>= codeSize; - } - else - { - // reload shift register - - // if start of new block - - if (bytesAvailable <= 0) - { - // read blocksize - bytesAvailable = Input[D++]; - - // exit if end of stream - if (bytesAvailable == 0) - return; - - // read block - CurBlock[(bytesAvailable - 1) / 4] = 0; // zero last entry - Buffer.BlockCopy(Input, D, CurBlock, 0, bytesAvailable); - blockPos = 0; - D += bytesAvailable; - } - - // load shift register - - shiftRegister = CurBlock[blockPos++]; - int newBits = bytesAvailable >= 4 ? 32 : bytesAvailable * 8; - bytesAvailable -= 4; - - // read remaining bits - - if (bitsAvailable > 0) - { - var bitsRemaining = codeSize - bitsAvailable; - curCode |= (shiftRegister << bitsAvailable) & mask; - shiftRegister >>= bitsRemaining; - bitsAvailable = newBits - bitsRemaining; - } - else - { - curCode = shiftRegister & mask; - shiftRegister >>= codeSize; - bitsAvailable = newBits - codeSize; - } - } - - // process code - - if (curCode == clearCode) - { - // reset codes - codeSize = minimumCodeSize + 1; - nextSize = Pow2[codeSize]; - numCodes = maximumCodeSize + 2; - - // reset buffer write pos - codesEnd = numCodes * 2; - - // clear previous code - previousCode = NoCode; - mask = (uint)(nextSize - 1); - - continue; - } - else if (curCode == endCode) - { - // stop - break; - } - - bool plusOne = false; - int codePos = 0; - - if (curCode < numCodes) - { - // write existing code - codePos = Indices[curCode]; - } - else if (previousCode != NoCode) - { - // write previous code - codePos = Indices[previousCode]; - plusOne = true; - } - else - continue; - - // output colours - - var codeLength = Codes[codePos++]; - var newCode = Codes[codePos]; - - for (int i = 0; i < codeLength; i++) - { - var code = Codes[codePos++]; - - if (code != TransparentIndex && col < Width) - { - Output[row + col] = ActiveColourTable[code]; - } - - if (++col == rightEdge) - { - col = ImageLeft; - row -= Width; - - if (row < 0) - { - SkipBlocks(); - return; - } - } - } - - if (plusOne) - { - if (newCode != TransparentIndex && col < Width) - Output[row + col] = ActiveColourTable[newCode]; - - if (++col == rightEdge) - { - col = ImageLeft; - row -= Width; - - if (row < 0) - break; - } - } - - // create new code - - if (previousCode != NoCode && numCodes != Indices.Length) - { - // get previous code from buffer - - codePos = Indices[previousCode]; - codeLength = Codes[codePos++]; - - // resize buffer if required (should be rare) - - if (codesEnd + codeLength + 1 >= Codes.Length) - Array.Resize(ref Codes, Codes.Length * 2); - - // add new code - - Indices[numCodes++] = codesEnd; - Codes[codesEnd++] = (ushort)(codeLength + 1); - - // copy previous code sequence - - var stop = codesEnd + codeLength; - - while (codesEnd < stop) - Codes[codesEnd++] = Codes[codePos++]; - - // append new code - - Codes[codesEnd++] = newCode; - } - - // increase code size? - - if (numCodes >= nextSize && codeSize < 12) - { - nextSize = Pow2[++codeSize]; - mask = (uint)(nextSize - 1); - } - - // remember last code processed - previousCode = curCode; - } - - // skip any remaining blocks - SkipBlocks(); - } - - public static string Ident() - { - var v = "1.1"; - var e = BitConverter.IsLittleEndian ? "L" : "B"; - var b = "M"; - var s = "S"; - var n = "2.0"; - - return $"{v} {e}{s}{b} {n}"; - } - } -} \ No newline at end of file diff --git a/Dependencies/MelonStartScreen/mgGif/Image.cs b/Dependencies/MelonStartScreen/mgGif/Image.cs deleted file mode 100644 index 705736f44..000000000 --- a/Dependencies/MelonStartScreen/mgGif/Image.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using MelonUnityEngine; - -namespace mgGif -{ - internal class Image : ICloneable - { - public int Width; - public int Height; - public int Delay; // milliseconds - public Color32[] RawImage; - - public Image() { } - - public Image(Image img) - { - Width = img.Width; - Height = img.Height; - Delay = img.Delay; - RawImage = img.RawImage != null ? (Color32[])img.RawImage.Clone() : null; - } - - public object Clone() => new Image(this); - - public Texture2D CreateTexture(FilterMode filterMode = FilterMode.Bilinear) - { - var tex = new Texture2D(Width, Height); - tex.filterMode = filterMode; - - Color[] colors = new Color[RawImage.Length]; - for (int i = 0; i < colors.Length; i++) - colors[i] = RawImage[i]; - - tex.SetPixels(colors); - tex.Apply(); - - return tex; - } - } -} diff --git a/Dependencies/MelonStartScreen/mgGif/LICENSE.txt b/Dependencies/MelonStartScreen/mgGif/LICENSE.txt deleted file mode 100644 index 28eeb1976..000000000 --- a/Dependencies/MelonStartScreen/mgGif/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Gwaredd Mountain - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/MelonLoader.sln b/MelonLoader.sln index 8dd74f911..935f0f4d9 100644 --- a/MelonLoader.sln +++ b/MelonLoader.sln @@ -16,11 +16,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Il2CppAssemblyGenerator", " {F9700790-414B-431B-9F9C-1D9210FAD682} = {F9700790-414B-431B-9F9C-1D9210FAD682} EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MelonStartScreen", "Dependencies\MelonStartScreen\MelonStartScreen.csproj", "{762D7545-6F6B-441A-B040-49CC31A1713B}" - ProjectSection(ProjectDependencies) = postProject - {F9700790-414B-431B-9F9C-1D9210FAD682} = {F9700790-414B-431B-9F9C-1D9210FAD682} - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono", "Dependencies\SupportModules\Mono\Mono.csproj", "{542EC51D-E480-4802-B5AA-96EEC05AF19C}" ProjectSection(ProjectDependencies) = postProject {F9700790-414B-431B-9F9C-1D9210FAD682} = {F9700790-414B-431B-9F9C-1D9210FAD682} @@ -81,8 +76,6 @@ Global {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|Any CPU.Build.0 = Release|Any CPU - {762D7545-6F6B-441A-B040-49CC31A1713B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {762D7545-6F6B-441A-B040-49CC31A1713B}.Release|Any CPU.ActiveCfg = Release|Any CPU {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|Any CPU.Build.0 = Debug|Any CPU {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -131,7 +124,6 @@ Global {8D8A18CB-7319-4220-BED8-6B3E23E6C19F} = {BE32DEBD-2C83-42F6-A1A2-941856586E57} {AB4D471C-9AB2-4A5F-93E1-8929E436C121} = {BE32DEBD-2C83-42F6-A1A2-941856586E57} {A6452A3F-4BD6-497A-97DA-24F7DF97B234} = {BE32DEBD-2C83-42F6-A1A2-941856586E57} - {762D7545-6F6B-441A-B040-49CC31A1713B} = {BE32DEBD-2C83-42F6-A1A2-941856586E57} {542EC51D-E480-4802-B5AA-96EEC05AF19C} = {8D8A18CB-7319-4220-BED8-6B3E23E6C19F} {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50} = {8D8A18CB-7319-4220-BED8-6B3E23E6C19F} {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA} = {AB4D471C-9AB2-4A5F-93E1-8929E436C121} From 83d7c2615b685737fc7cc1ba8e95c502039365aa Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 19:34:36 +0100 Subject: [PATCH 04/18] Cleanup NativeHost --- .editorconfig | 1 + MelonLoader.NativeHost/NativeEntryPoint.cs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.editorconfig b/.editorconfig index 850612115..795986ade 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,6 +2,7 @@ # CS1591: Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.IDE0058.severity = none csharp_indent_labels = one_less_than_current csharp_using_directive_placement = outside_namespace:warning csharp_prefer_simple_using_statement = true:warning diff --git a/MelonLoader.NativeHost/NativeEntryPoint.cs b/MelonLoader.NativeHost/NativeEntryPoint.cs index 4e3b72c57..dbac41ec4 100644 --- a/MelonLoader.NativeHost/NativeEntryPoint.cs +++ b/MelonLoader.NativeHost/NativeEntryPoint.cs @@ -1,8 +1,8 @@ -using System.Reflection; +using MelonLoader.InternalUtils; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; -using MelonLoader.InternalUtils; namespace MelonLoader.NativeHost; @@ -20,13 +20,13 @@ private static void NativeEntry(nint* startFunc) var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(currentAsm.Location); var type = asm.GetType("MelonLoader.NativeHost.NativeEntryPoint", true)!; var init = type.GetMethod(nameof(Initialize), BindingFlags.Static | BindingFlags.NonPublic)!; - init.Invoke(null, [ (nint)startFunc]); + init.Invoke(null, [(nint)startFunc]); } - private unsafe static void Initialize(nint* startFunc) + private static unsafe void Initialize(nint* startFunc) { AssemblyLoadContext.Default.Resolving += OnResolveAssembly; - + //Have to invoke through a proxy so that we don't load MelonLoader.dll before the above line CallInit(startFunc); } From 88aa3e8aa6f7cdc5d1adbff76c1c0189873dd81e Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 19:57:09 +0100 Subject: [PATCH 05/18] Cleanup Bootstrap --- .editorconfig | 10 ++++++---- MelonLoader.Bootstrap/Logging/MelonLogger.cs | 4 +--- .../RuntimeHandlers/Il2Cpp/Dotnet.cs | 5 +---- .../RuntimeHandlers/Il2Cpp/DotnetInstaller.cs | 2 +- .../RuntimeHandlers/Il2Cpp/Il2CppLib.cs | 19 +++++++++---------- .../RuntimeHandlers/Mono/MonoLib.cs | 5 +---- MelonLoader.Bootstrap/Utils/Dobby.cs | 12 +++--------- MelonLoader/LoaderConfig.cs | 1 - 8 files changed, 22 insertions(+), 36 deletions(-) diff --git a/.editorconfig b/.editorconfig index 795986ade..ea4c4bec7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,14 +12,14 @@ csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = false:error csharp_style_prefer_primary_constructors = true:suggestion csharp_prefer_system_threading_lock = true:suggestion -csharp_style_expression_bodied_methods = false:silent -csharp_style_expression_bodied_constructors = false:silent -csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent -csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_local_functions = true:silent csharp_space_around_binary_operators = before_and_after csharp_style_throw_expression = true:suggestion csharp_style_prefer_null_check_over_type_check = true:suggestion @@ -56,6 +56,8 @@ csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion dotnet_diagnostic.CA1507.severity = warning dotnet_diagnostic.CA1845.severity = warning +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true [*.{cs,vb}] #### Naming styles #### diff --git a/MelonLoader.Bootstrap/Logging/MelonLogger.cs b/MelonLoader.Bootstrap/Logging/MelonLogger.cs index 58f95484c..32092e3aa 100644 --- a/MelonLoader.Bootstrap/Logging/MelonLogger.cs +++ b/MelonLoader.Bootstrap/Logging/MelonLogger.cs @@ -52,9 +52,7 @@ public static void Init() queue.Sort((x, y) => { - if (x.Item2 >= y.Item2) - return 0; - return 1; + return x.Item2 >= y.Item2 ? 0 : 1; }); var toDelete = logs.Length - LoaderConfig.Current.Logs.MaxLogs + 1; diff --git a/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Dotnet.cs b/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Dotnet.cs index b3bbe1010..4623f6331 100644 --- a/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Dotnet.cs +++ b/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Dotnet.cs @@ -58,10 +58,7 @@ public static bool InitializeForRuntimeConfig(string runtimeConfigPath, out nint nint funcPtr = 0; loadAssemblyAndGetFunctionPointer(assemblyPath, typeName, methodName, -1, 0, ref funcPtr); - if (funcPtr == 0) - return null; - - return Marshal.GetDelegateForFunctionPointer(funcPtr); + return funcPtr == 0 ? null : Marshal.GetDelegateForFunctionPointer(funcPtr); } [DllImport("*", CharSet = hostfxrCharSet)] diff --git a/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/DotnetInstaller.cs b/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/DotnetInstaller.cs index 43ab0b54f..d060ea19e 100644 --- a/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/DotnetInstaller.cs +++ b/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/DotnetInstaller.cs @@ -4,7 +4,7 @@ namespace MelonLoader.Bootstrap.RuntimeHandlers.Il2Cpp; internal static class DotnetInstaller { - private readonly static string dotnetRuntimeDownload = + private static readonly string dotnetRuntimeDownload = #if X64 "https://aka.ms/dotnet/6.0/dotnet-runtime-win-x64.exe"; #else diff --git a/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Il2CppLib.cs b/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Il2CppLib.cs index 735a803de..36ab52515 100644 --- a/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Il2CppLib.cs +++ b/MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Il2CppLib.cs @@ -19,18 +19,17 @@ internal class Il2CppLib(Il2CppLib.MethodGetNameFn methodGetName) public static Il2CppLib? TryLoad() { - if (!NativeLibrary.TryLoad(libName, out var hRuntime) + return !NativeLibrary.TryLoad(libName, out var hRuntime) || !NativeLibrary.TryGetExport(hRuntime, "il2cpp_init", out var initPtr) || !NativeLibrary.TryGetExport(hRuntime, "il2cpp_runtime_invoke", out var runtimeInvokePtr) - || !NativeFunc.GetExport(hRuntime, "il2cpp_method_get_name", out var methodGetName)) - return null; - - return new(methodGetName) - { - Handle = hRuntime, - InitPtr = initPtr, - RuntimeInvokePtr = runtimeInvokePtr - }; + || !NativeFunc.GetExport(hRuntime, "il2cpp_method_get_name", out var methodGetName) + ? null + : new(methodGetName) + { + Handle = hRuntime, + InitPtr = initPtr, + RuntimeInvokePtr = runtimeInvokePtr + }; } public string? GetMethodName(nint method) diff --git a/MelonLoader.Bootstrap/RuntimeHandlers/Mono/MonoLib.cs b/MelonLoader.Bootstrap/RuntimeHandlers/Mono/MonoLib.cs index 0692bbb09..e7bea4000 100644 --- a/MelonLoader.Bootstrap/RuntimeHandlers/Mono/MonoLib.cs +++ b/MelonLoader.Bootstrap/RuntimeHandlers/Mono/MonoLib.cs @@ -163,10 +163,7 @@ public void AddManagedInternalCall(string name, TDelegate func) where public string? GetMethodName(nint method) { - if (method == 0) - return null; - - return Marshal.PtrToStringAnsi(MethodGetName(method)); + return method == 0 ? null : Marshal.PtrToStringAnsi(MethodGetName(method)); } public void InstallAssemblyHooks(AssemblyPreloadHookFn? preloadHook, AssemblySearchHookFn? searchHook, AssemblyLoadHookFn? loadHook) diff --git a/MelonLoader.Bootstrap/Utils/Dobby.cs b/MelonLoader.Bootstrap/Utils/Dobby.cs index 202a5186c..7328802b8 100644 --- a/MelonLoader.Bootstrap/Utils/Dobby.cs +++ b/MelonLoader.Bootstrap/Utils/Dobby.cs @@ -20,15 +20,9 @@ public static unsafe partial class Dobby public static nint HookAttach(nint target, nint detour) { nint original = 0; - if (Prepare(target, detour, &original) != 0) - { - throw new AccessViolationException($"Could not prepare patch to target {target:X}"); - } - if (Commit(target) != 0) - { - throw new AccessViolationException($"Could not commit patch to target {target:X}"); - } - return original; + return Prepare(target, detour, &original) != 0 + ? throw new AccessViolationException($"Could not prepare patch to target {target:X}") + : Commit(target) != 0 ? throw new AccessViolationException($"Could not commit patch to target {target:X}") : original; } public static void HookDetach(nint target) diff --git a/MelonLoader/LoaderConfig.cs b/MelonLoader/LoaderConfig.cs index 2347689e5..334b49975 100644 --- a/MelonLoader/LoaderConfig.cs +++ b/MelonLoader/LoaderConfig.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; using Tomlet.Attributes; From eabaf4149dd50bd95bf3bd76177af5aa65c10b05 Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 20:01:20 +0100 Subject: [PATCH 06/18] Cleanup UnityUtilities --- .../Il2CppAssetBundle.cs | 96 +++++++++---------- .../Il2CppAssetBundleManager.cs | 14 +-- .../Il2CppAssetBundleRequest.cs | 12 +-- .../Il2CppImageConversionManager.cs | 40 +++----- 4 files changed, 72 insertions(+), 90 deletions(-) diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs index 366dfb888..d985afb87 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs @@ -29,11 +29,11 @@ public bool isStreamedSceneAssetBundle { get { - if (bundleptr == IntPtr.Zero) - throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); - if (get_isStreamedSceneAssetBundleDelegateField == null) - throw new NullReferenceException("The get_isStreamedSceneAssetBundleDelegateField cannot be null."); - return get_isStreamedSceneAssetBundleDelegateField(bundleptr); + return bundleptr == IntPtr.Zero + ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero") + : get_isStreamedSceneAssetBundleDelegateField == null + ? throw new NullReferenceException("The get_isStreamedSceneAssetBundleDelegateField cannot be null.") + : get_isStreamedSceneAssetBundleDelegateField(bundleptr); } } @@ -46,7 +46,7 @@ public Object mainAsset if (returnMainAssetDelegateField == null) throw new NullReferenceException("The returnMainAssetDelegateField cannot be null."); var intPtr = returnMainAssetDelegateField(bundleptr); - return ((intPtr != IntPtr.Zero) ? new Object(intPtr) : null); + return (intPtr != IntPtr.Zero) ? new Object(intPtr) : null; } } @@ -54,11 +54,11 @@ public bool Contains(string name) { if (bundleptr == IntPtr.Zero) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); - if (string.IsNullOrEmpty(name)) - throw new ArgumentException("The input asset name cannot be null or empty."); - if (ContainsDelegateField == null) - throw new NullReferenceException("The ContainsDelegateField cannot be null."); - return ContainsDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name)); + return string.IsNullOrEmpty(name) + ? throw new ArgumentException("The input asset name cannot be null or empty.") + : ContainsDelegateField == null + ? throw new NullReferenceException("The ContainsDelegateField cannot be null.") + : ContainsDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name)); } public Il2CppStringArray AllAssetNames() => GetAllAssetNames(); @@ -70,7 +70,7 @@ public Il2CppStringArray GetAllAssetNames() if (GetAllAssetNamesDelegateField == null) throw new NullReferenceException("The GetAllAssetNamesDelegateField cannot be null."); var intPtr = GetAllAssetNamesDelegateField(bundleptr); - return ((intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null); + return (intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null; } public Il2CppStringArray AllScenePaths() => GetAllScenePaths(); @@ -82,7 +82,7 @@ public Il2CppStringArray GetAllScenePaths() if (GetAllScenePathsDelegateField == null) throw new NullReferenceException("The GetAllScenePathsDelegateField cannot be null."); var intPtr = GetAllScenePathsDelegateField(bundleptr); - return ((intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null); + return (intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null; } public Object Load(string name) => LoadAsset(name); @@ -96,7 +96,7 @@ public T LoadAsset(string name) where T : Object if (!InteropSupport.IsGeneratedAssemblyType(typeof(T))) throw new NullReferenceException("The type must be a Generated Assembly Type."); var intptr = LoadAsset(name, Il2CppType.Of().Pointer); - return ((intptr != IntPtr.Zero) ? InteropSupport.Il2CppObjectPtrToIl2CppObject(intptr) : null); + return (intptr != IntPtr.Zero) ? InteropSupport.Il2CppObjectPtrToIl2CppObject(intptr) : null; } public Object Load(string name, Il2CppSystem.Type type) => LoadAsset(name, type); @@ -106,7 +106,7 @@ public Object LoadAsset(string name, Il2CppSystem.Type type) if (type == null) throw new NullReferenceException("The input type cannot be null."); var intptr = LoadAsset(name, type.Pointer); - return ((intptr != IntPtr.Zero) ? new Object(intptr) : null); + return (intptr != IntPtr.Zero) ? new Object(intptr) : null; } public IntPtr Load(string name, IntPtr typeptr) => LoadAsset(name, typeptr); @@ -117,11 +117,11 @@ public IntPtr LoadAsset(string name, IntPtr typeptr) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (string.IsNullOrEmpty(name)) throw new ArgumentException("The input asset name cannot be null or empty."); - if (typeptr == IntPtr.Zero) - throw new NullReferenceException("The input type cannot be IntPtr.Zero"); - if (LoadAsset_InternalDelegateField == null) - throw new NullReferenceException("The LoadAsset_InternalDelegateField cannot be null."); - return LoadAsset_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + return typeptr == IntPtr.Zero + ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") + : LoadAsset_InternalDelegateField == null + ? throw new NullReferenceException("The LoadAsset_InternalDelegateField cannot be null.") + : LoadAsset_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public Il2CppAssetBundleRequest LoadAssetAsync(string name) => LoadAssetAsync(name); @@ -131,7 +131,7 @@ public Il2CppAssetBundleRequest LoadAssetAsync(string name) where T : Object if (!InteropSupport.IsGeneratedAssemblyType(typeof(T))) throw new NullReferenceException("The type must be a Generated Assembly Type."); var intptr = LoadAssetAsync(name, Il2CppType.Of().Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null; } public Il2CppAssetBundleRequest LoadAssetAsync(string name, Il2CppSystem.Type type) @@ -139,7 +139,7 @@ public Il2CppAssetBundleRequest LoadAssetAsync(string name, Il2CppSystem.Type ty if (type == null) throw new NullReferenceException("The input type cannot be null."); var intptr = LoadAssetAsync(name, type.Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null; } public IntPtr LoadAssetAsync(string name, IntPtr typeptr) @@ -148,11 +148,11 @@ public IntPtr LoadAssetAsync(string name, IntPtr typeptr) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (string.IsNullOrEmpty(name)) throw new ArgumentException("The input asset name cannot be null or empty."); - if (typeptr == IntPtr.Zero) - throw new NullReferenceException("The input type cannot be IntPtr.Zero"); - if (LoadAssetAsync_InternalDelegateField == null) - throw new NullReferenceException("The LoadAssetAsync_InternalDelegateField cannot be null."); - return LoadAssetAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + return typeptr == IntPtr.Zero + ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") + : LoadAssetAsync_InternalDelegateField == null + ? throw new NullReferenceException("The LoadAssetAsync_InternalDelegateField cannot be null.") + : LoadAssetAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public Il2CppReferenceArray LoadAll() => LoadAllAssets(); @@ -166,7 +166,7 @@ public Il2CppReferenceArray LoadAllAssets() where T : Object if (!InteropSupport.IsGeneratedAssemblyType(typeof(T))) throw new NullReferenceException("The type must be a Generated Assembly Type."); var intptr = LoadAllAssets(Il2CppType.Of().Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null; } public Il2CppReferenceArray LoadAll(Il2CppSystem.Type type) => LoadAllAssets(type); @@ -176,18 +176,18 @@ public Il2CppReferenceArray LoadAllAssets(Il2CppSystem.Type type) if (type == null) throw new NullReferenceException("The input type cannot be null."); var intptr = LoadAllAssets(type.Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null; } public IntPtr LoadAll(IntPtr typeptr) => LoadAllAssets(typeptr); public IntPtr LoadAllAssets(IntPtr typeptr) { - if (typeptr == IntPtr.Zero) - throw new NullReferenceException("The input type cannot be IntPtr.Zero"); - if (LoadAssetWithSubAssets_InternalDelegateField == null) - throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null."); - return LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(string.Empty), typeptr); + return typeptr == IntPtr.Zero + ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") + : LoadAssetWithSubAssets_InternalDelegateField == null + ? throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null.") + : LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(string.Empty), typeptr); } public Il2CppReferenceArray LoadAssetWithSubAssets(string name) => LoadAssetWithSubAssets(name); @@ -197,7 +197,7 @@ public Il2CppReferenceArray LoadAssetWithSubAssets(string name) where T : if (!InteropSupport.IsGeneratedAssemblyType(typeof(T))) throw new NullReferenceException("The type must be a Generated Assembly Type."); var intptr = LoadAssetWithSubAssets(name, Il2CppType.Of().Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null; } public Il2CppReferenceArray LoadAssetWithSubAssets(string name, Il2CppSystem.Type type) @@ -205,7 +205,7 @@ public Il2CppReferenceArray LoadAssetWithSubAssets(string name, Il2CppSy if (type == null) throw new NullReferenceException("The input type cannot be null."); var intptr = LoadAssetWithSubAssets(name, type.Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null; } public IntPtr LoadAssetWithSubAssets(string name, IntPtr typeptr) @@ -214,11 +214,11 @@ public IntPtr LoadAssetWithSubAssets(string name, IntPtr typeptr) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (string.IsNullOrEmpty(name)) throw new ArgumentException("The input asset name cannot be null or empty."); - if (typeptr == IntPtr.Zero) - throw new NullReferenceException("The input type cannot be IntPtr.Zero"); - if (LoadAssetWithSubAssets_InternalDelegateField == null) - throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null."); - return LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + return typeptr == IntPtr.Zero + ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") + : LoadAssetWithSubAssets_InternalDelegateField == null + ? throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null.") + : LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name) => LoadAssetWithSubAssetsAsync(name); @@ -227,7 +227,7 @@ public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name) wher if (!InteropSupport.IsGeneratedAssemblyType(typeof(T))) throw new NullReferenceException("The type must be a Generated Assembly Type."); var intptr = LoadAssetWithSubAssetsAsync(name, Il2CppType.Of().Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null; } public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name, Il2CppSystem.Type type) @@ -235,7 +235,7 @@ public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name, Il2CppS if (type == null) throw new NullReferenceException("The input type cannot be null."); var intptr = LoadAssetWithSubAssetsAsync(name, type.Pointer); - return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + return (intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null; } public IntPtr LoadAssetWithSubAssetsAsync(string name, IntPtr typeptr) @@ -244,11 +244,11 @@ public IntPtr LoadAssetWithSubAssetsAsync(string name, IntPtr typeptr) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (string.IsNullOrEmpty(name)) throw new ArgumentException("The input asset name cannot be null or empty."); - if (typeptr == IntPtr.Zero) - throw new NullReferenceException("The input type cannot be IntPtr.Zero"); - if (LoadAssetWithSubAssetsAsync_InternalDelegateField == null) - throw new NullReferenceException("The LoadAssetWithSubAssetsAsync_InternalDelegateField cannot be null."); - return LoadAssetWithSubAssetsAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + return typeptr == IntPtr.Zero + ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") + : LoadAssetWithSubAssetsAsync_InternalDelegateField == null + ? throw new NullReferenceException("The LoadAssetWithSubAssetsAsync_InternalDelegateField cannot be null.") + : LoadAssetWithSubAssetsAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public void Unload(bool unloadAllLoadedObjects) diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs index 70956fa94..092bf4238 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs @@ -23,7 +23,7 @@ public static Il2CppAssetBundle[] GetAllLoadedAssetBundles() if (GetAllLoadedAssetBundles_NativeDelegateField == null) throw new System.NullReferenceException("The GetAllLoadedAssetBundles_NativeDelegateField cannot be null."); var intPtr = GetAllLoadedAssetBundles_NativeDelegateField(); - var refarr = ((intPtr != System.IntPtr.Zero) ? new Il2CppReferenceArray(intPtr) : null); + var refarr = (intPtr != System.IntPtr.Zero) ? new Il2CppReferenceArray(intPtr) : null; if (refarr == null) throw new System.NullReferenceException("The refarr cannot be null."); System.Collections.Generic.List bundlelist = []; @@ -43,7 +43,7 @@ public static Il2CppAssetBundle LoadFromFile(string path, uint crc, ulong offset if (LoadFromFile_InternalDelegateField == null) throw new System.NullReferenceException("The LoadFromFile_InternalDelegateField cannot be null."); var intPtr = LoadFromFile_InternalDelegateField(IL2CPP.ManagedStringToIl2Cpp(path), crc, offset); - return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null); + return (intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null; } public static Il2CppAssetBundleCreateRequest LoadFromFileAsync(string path) => LoadFromFileAsync(path, 0u, 0UL); @@ -57,7 +57,7 @@ public static Il2CppAssetBundleCreateRequest LoadFromFileAsync(string path, uint if (LoadFromFileAsync_InternalDelegateField == null) throw new System.NullReferenceException("The LoadFromFileAsync_InternalDelegateField cannot be null."); var intPtr = LoadFromFileAsync_InternalDelegateField(IL2CPP.ManagedStringToIl2Cpp(path), crc, offset); - return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null); + return (intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null; } public static Il2CppAssetBundle LoadFromMemory(Il2CppStructArray binary) => LoadFromMemory(binary, 0u); @@ -69,7 +69,7 @@ public static Il2CppAssetBundle LoadFromMemory(Il2CppStructArray binary, u if (LoadFromMemory_InternalDelegateField == null) throw new System.NullReferenceException("The LoadFromMemory_InternalDelegateField cannot be null."); var intPtr = LoadFromMemory_InternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(binary), crc); - return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null); + return (intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null; } public static Il2CppAssetBundleCreateRequest LoadFromMemoryAsync(Il2CppStructArray binary) => LoadFromMemoryAsync(binary, 0u); @@ -81,7 +81,7 @@ public static Il2CppAssetBundleCreateRequest LoadFromMemoryAsync(Il2CppStructArr if (LoadFromMemoryAsync_InternalDelegateField == null) throw new System.NullReferenceException("The LoadFromMemoryAsync_InternalDelegateField cannot be null."); var intPtr = LoadFromMemoryAsync_InternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(binary), crc); - return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null); + return (intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null; } public static Il2CppAssetBundle LoadFromStream(Stream stream) => LoadFromStream(stream, 0u, 0u); @@ -95,7 +95,7 @@ public static Il2CppAssetBundle LoadFromStream(Stream stream, uint crc, uint man if (LoadFromStreamInternalDelegateField == null) throw new System.NullReferenceException("The LoadFromStreamInternalDelegateField cannot be null."); var intPtr = LoadFromStreamInternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(stream), crc, managedReadBufferSize); - return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null); + return (intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null; } public static Il2CppAssetBundleCreateRequest LoadFromStreamAsync(Stream stream) => LoadFromStreamAsync(stream, 0u, 0u); @@ -109,7 +109,7 @@ public static Il2CppAssetBundleCreateRequest LoadFromStreamAsync(Stream stream, if (LoadFromStreamAsyncInternalDelegateField == null) throw new System.NullReferenceException("The LoadFromStreamAsyncInternalDelegateField cannot be null."); var intPtr = LoadFromStreamAsyncInternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(stream), crc, managedReadBufferSize); - return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null); + return (intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null; } public static void UnloadAllAssetBundles(bool unloadAllObjects) diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs index 642403b34..ff210d22d 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs @@ -21,9 +21,7 @@ public Il2CppAssetBundle assetBundle get { var ptr = get_assetBundleDelegateField(this.Pointer); - if (ptr == IntPtr.Zero) - return null; - return new Il2CppAssetBundle(ptr); + return ptr == IntPtr.Zero ? null : new Il2CppAssetBundle(ptr); } } @@ -48,9 +46,7 @@ public Object asset get { var ptr = get_assetDelegateField(this.Pointer); - if (ptr == IntPtr.Zero) - return null; - return new Object(ptr); + return ptr == IntPtr.Zero ? null : new Object(ptr); } } @@ -59,9 +55,7 @@ public Il2CppReferenceArray allAssets get { var ptr = get_allAssetsDelegateField(this.Pointer); - if (ptr == IntPtr.Zero) - return null; - return new Il2CppReferenceArray(ptr); + return ptr == IntPtr.Zero ? null : new Il2CppReferenceArray(ptr); } } diff --git a/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs b/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs index 01128bc5b..eb5ebfd61 100644 --- a/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs +++ b/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs @@ -23,10 +23,7 @@ public static Il2CppStructArray EncodeToTGA(Texture2D tex) throw new NullReferenceException("The EncodeToTGADelegateField cannot be null."); Il2CppStructArray il2CppStructArray; var encodeToTGADelegateField = EncodeToTGADelegateField(IL2CPP.Il2CppObjectBaseToPtr(tex)); - if (encodeToTGADelegateField != IntPtr.Zero) - il2CppStructArray = new Il2CppStructArray(encodeToTGADelegateField); - else - il2CppStructArray = null; + il2CppStructArray = encodeToTGADelegateField != IntPtr.Zero ? new Il2CppStructArray(encodeToTGADelegateField) : null; return il2CppStructArray; } @@ -38,10 +35,7 @@ public static Il2CppStructArray EncodeToPNG(Texture2D tex) throw new NullReferenceException("The EncodeToPNGDelegateField cannot be null."); Il2CppStructArray il2CppStructArray; var encodeToPNGDelegateField = EncodeToPNGDelegateField(IL2CPP.Il2CppObjectBaseToPtr(tex)); - if (encodeToPNGDelegateField != IntPtr.Zero) - il2CppStructArray = new Il2CppStructArray(encodeToPNGDelegateField); - else - il2CppStructArray = null; + il2CppStructArray = encodeToPNGDelegateField != IntPtr.Zero ? new Il2CppStructArray(encodeToPNGDelegateField) : null; return il2CppStructArray; } @@ -53,10 +47,7 @@ public static Il2CppStructArray EncodeToJPG(Texture2D tex, int quality) throw new NullReferenceException("The EncodeToJPGDelegateField cannot be null."); Il2CppStructArray il2CppStructArray; var encodeToJPGDelegateField = EncodeToJPGDelegateField(IL2CPP.Il2CppObjectBaseToPtr(tex), quality); - if (encodeToJPGDelegateField != IntPtr.Zero) - il2CppStructArray = new Il2CppStructArray(encodeToJPGDelegateField); - else - il2CppStructArray = null; + il2CppStructArray = encodeToJPGDelegateField != IntPtr.Zero ? new Il2CppStructArray(encodeToJPGDelegateField) : null; return il2CppStructArray; } public static Il2CppStructArray EncodeToJPG(Texture2D tex) => EncodeToJPG(tex, 75); @@ -69,10 +60,7 @@ public static Il2CppStructArray EncodeToEXR(Texture2D tex, Texture2D.EXRFl throw new NullReferenceException("The EncodeToEXRDelegateField cannot be null."); Il2CppStructArray il2CppStructArray; var encodeToEXRDelegateField = EncodeToEXRDelegateField(IL2CPP.Il2CppObjectBaseToPtr(tex), flags); - if (encodeToEXRDelegateField != IntPtr.Zero) - il2CppStructArray = new Il2CppStructArray(encodeToEXRDelegateField); - else - il2CppStructArray = null; + il2CppStructArray = encodeToEXRDelegateField != IntPtr.Zero ? new Il2CppStructArray(encodeToEXRDelegateField) : null; return il2CppStructArray; } public static Il2CppStructArray EncodeToEXR(Texture2D tex) => EncodeToEXR(tex, 0); @@ -81,11 +69,11 @@ public static bool LoadImage(Texture2D tex, Il2CppStructArray data, bool m { if (tex == null) throw new ArgumentException("The texture cannot be null."); - if (data == null) - throw new ArgumentException("The data cannot be null."); - if (LoadImageDelegateField == null) - throw new NullReferenceException("The LoadImageDelegateField cannot be null."); - return LoadImageDelegateField(IL2CPP.Il2CppObjectBaseToPtr(tex), IL2CPP.Il2CppObjectBaseToPtr(data), markNonReadable); + return data == null + ? throw new ArgumentException("The data cannot be null.") + : LoadImageDelegateField == null + ? throw new NullReferenceException("The LoadImageDelegateField cannot be null.") + : LoadImageDelegateField(IL2CPP.Il2CppObjectBaseToPtr(tex), IL2CPP.Il2CppObjectBaseToPtr(data), markNonReadable); } public static bool LoadImage(Texture2D tex, Il2CppStructArray data) => LoadImage(tex, data, false); @@ -93,9 +81,9 @@ public static bool LoadImage(Texture2D tex, Il2CppStructArray data, bool m private delegate IntPtr TextureAndQualityDelegate(IntPtr tex, int quality); private delegate IntPtr TextureAndFlagDelegate(IntPtr tex, Texture2D.EXRFlags flags); private delegate bool LoadImageDelegate(IntPtr tex, IntPtr data, bool markNonReadable); - private readonly static TextureAndFlagDelegate EncodeToEXRDelegateField; - private readonly static TextureOnlyDelegate EncodeToTGADelegateField; - private readonly static TextureOnlyDelegate EncodeToPNGDelegateField; - private readonly static TextureAndQualityDelegate EncodeToJPGDelegateField; - private readonly static LoadImageDelegate LoadImageDelegateField; + private static readonly TextureAndFlagDelegate EncodeToEXRDelegateField; + private static readonly TextureOnlyDelegate EncodeToTGADelegateField; + private static readonly TextureOnlyDelegate EncodeToPNGDelegateField; + private static readonly TextureAndQualityDelegate EncodeToJPGDelegateField; + private static readonly LoadImageDelegate LoadImageDelegateField; } \ No newline at end of file From 328a98c5843df9241d3eb635654bf81610d3af09 Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 20:14:21 +0100 Subject: [PATCH 07/18] Cleanup Dependencies --- .editorconfig | 8 +- .../CompatibilityLayers/Demeo/Module.cs | 193 +++++----- .../CompatibilityLayers/IPA/IPA/ModPrefs.cs | 83 ++--- .../IPA/IPA/PluginManager.cs | 6 +- .../IPA/IPAPluginWrapper.cs | 35 +- .../CompatibilityLayers/IPA/Module.cs | 145 ++++---- .../Muse_Dash_Mono/Module.cs | 128 +++---- .../MuseDashModLoader/ModLoader.cs | 18 +- .../MuseDashModLoader/ModLogger.cs | 2 +- .../Stress_Level_Zero_Il2Cpp/Module.cs | 105 +++--- .../Il2CppAssemblyGenerator/Config.cs | 59 ++- Dependencies/Il2CppAssemblyGenerator/Core.cs | 264 +++++++------ .../Il2CppAssemblyGenerator/Extensions.cs | 2 +- .../Il2CppAssemblyGenerator/FileHandler.cs | 191 +++++----- .../Packages/Cpp2IL.cs | 117 +++--- .../Packages/Cpp2IL_NetFramework.cs | 171 ++++----- .../Packages/Cpp2IL_StrippedCodeRegSupport.cs | 73 ++-- .../Packages/DeobfuscationMap.cs | 63 ++-- .../Packages/DeobfuscationRegex.cs | 61 ++- .../Packages/Il2CppInterop.cs | 223 +++++------ .../Packages/Models/ExecutablePackage.cs | 225 +++++------ .../Packages/Models/PackageBase.cs | 87 +++-- .../Packages/UnityDependencies.cs | 31 +- .../Il2CppAssemblyGenerator/RemoteAPI.cs | 227 ++++++------ Dependencies/SupportModules/Component.cs | 266 ++++++------- .../SupportModules/Il2Cpp/InteropInterface.cs | 72 ++-- Dependencies/SupportModules/Il2Cpp/Main.cs | 348 +++++++++--------- .../Il2Cpp/MonoEnumeratorWrapper.cs | 82 ++--- Dependencies/SupportModules/Mono/Main.cs | 63 ++-- Dependencies/SupportModules/SceneHandler.cs | 125 ++++--- .../SupportModules/SupportModule_To.cs | 37 +- Dependencies/SupportModules/UnityMappers.cs | 155 ++++---- 32 files changed, 1820 insertions(+), 1845 deletions(-) diff --git a/.editorconfig b/.editorconfig index ea4c4bec7..31946e2e8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,14 +12,14 @@ csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = false:error csharp_style_prefer_primary_constructors = true:suggestion csharp_prefer_system_threading_lock = true:suggestion -csharp_style_expression_bodied_methods = true:silent -csharp_style_expression_bodied_constructors = true:silent -csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent -csharp_style_expression_bodied_local_functions = true:silent +csharp_style_expression_bodied_local_functions = false:silent csharp_space_around_binary_operators = before_and_after csharp_style_throw_expression = true:suggestion csharp_style_prefer_null_check_over_type_check = true:suggestion diff --git a/Dependencies/CompatibilityLayers/Demeo/Module.cs b/Dependencies/CompatibilityLayers/Demeo/Module.cs index 8bd856fde..ea1902b3f 100644 --- a/Dependencies/CompatibilityLayers/Demeo/Module.cs +++ b/Dependencies/CompatibilityLayers/Demeo/Module.cs @@ -1,131 +1,130 @@ -using System; +using MelonLoader.Modules; +using System; using System.Collections; using System.Collections.Generic; using System.Reflection; -using MelonLoader.Modules; [assembly: MelonLoader.PatchShield] -namespace MelonLoader.CompatibilityLayers +namespace MelonLoader.CompatibilityLayers; + +internal class Demeo_Module : MelonModule { - internal class Demeo_Module : MelonModule - { - private Dictionary ModInformation = new Dictionary(); - private IList ModInfoList; + private readonly Dictionary ModInformation = []; + private IList ModInfoList; - private Type modInfoType; - private FieldInfo name_field; - private FieldInfo version_field; - private FieldInfo author_field; - private FieldInfo description_field; - private FieldInfo isNetworkCompatible_field; + private Type modInfoType; + private FieldInfo name_field; + private FieldInfo version_field; + private FieldInfo author_field; + private FieldInfo description_field; + private FieldInfo isNetworkCompatible_field; - public override void OnInitialize() - { - MelonEvents.OnApplicationStart.Subscribe(OnPreAppStart, int.MaxValue); - MelonBase.OnMelonRegistered.Subscribe(ParseMelon, int.MaxValue); - MelonBase.OnMelonUnregistered.Subscribe(OnUnregister, int.MaxValue); - } + public override void OnInitialize() + { + MelonEvents.OnApplicationStart.Subscribe(OnPreAppStart, int.MaxValue); + MelonBase.OnMelonRegistered.Subscribe(ParseMelon, int.MaxValue); + MelonBase.OnMelonUnregistered.Subscribe(OnUnregister, int.MaxValue); + } - private void OnPreAppStart() + private void OnPreAppStart() + { + try { - try + var assembly = Assembly.Load("Assembly-CSharp"); + var moddingApi = assembly.GetType("Boardgame.Modding.ModdingAPI"); + + modInfoType = moddingApi.GetNestedType("ModInformation"); + name_field = modInfoType.GetField("name", BindingFlags.Public | BindingFlags.Instance); + version_field = modInfoType.GetField("version", BindingFlags.Public | BindingFlags.Instance); + author_field = modInfoType.GetField("author", BindingFlags.Public | BindingFlags.Instance); + description_field = modInfoType.GetField("description", BindingFlags.Public | BindingFlags.Instance); + isNetworkCompatible_field = modInfoType.GetField("isNetworkCompatible", BindingFlags.Public | BindingFlags.Instance); + + var externalModsField = moddingApi.GetField("ExternallyInstalledMods", BindingFlags.Public | BindingFlags.Static); + if (externalModsField.GetValue(null) == null) { - Assembly assembly = Assembly.Load("Assembly-CSharp"); - Type moddingApi = assembly.GetType("Boardgame.Modding.ModdingAPI"); - - modInfoType = moddingApi.GetNestedType("ModInformation"); - name_field = modInfoType.GetField("name", BindingFlags.Public | BindingFlags.Instance); - version_field = modInfoType.GetField("version", BindingFlags.Public | BindingFlags.Instance); - author_field = modInfoType.GetField("author", BindingFlags.Public | BindingFlags.Instance); - description_field = modInfoType.GetField("description", BindingFlags.Public | BindingFlags.Instance); - isNetworkCompatible_field = modInfoType.GetField("isNetworkCompatible", BindingFlags.Public | BindingFlags.Instance); - - FieldInfo externalModsField = moddingApi.GetField("ExternallyInstalledMods", BindingFlags.Public | BindingFlags.Static); - if (externalModsField.GetValue(null) == null) - { - var listType = typeof(List<>); - var constructedListType = listType.MakeGenericType(modInfoType); - ModInfoList = (IList)Activator.CreateInstance(constructedListType); - externalModsField.SetValue(null, ModInfoList); - } + var listType = typeof(List<>); + var constructedListType = listType.MakeGenericType(modInfoType); + ModInfoList = (IList)Activator.CreateInstance(constructedListType); + externalModsField.SetValue(null, ModInfoList); + } - foreach (var m in MelonPlugin.RegisteredMelons) + foreach (var m in MelonPlugin.RegisteredMelons) + { + try { - try - { - ParseMelon(m); - } - catch (Exception ex) - { - MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); - } + ParseMelon(m); } - - foreach (var m in MelonMod.RegisteredMelons) + catch (Exception ex) { - try - { - ParseMelon(m); - } - catch (Exception ex) - { - MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); - } + MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); } } - catch (Exception ex) + + foreach (var m in MelonMod.RegisteredMelons) { - MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); + try + { + ParseMelon(m); + } + catch (Exception ex) + { + MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); + } } } - - private void OnUnregister(MelonBase melon) + catch (Exception ex) { - if (melon == null) - return; + MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); + } + } - if (!ModInformation.ContainsKey(melon)) - return; + private void OnUnregister(MelonBase melon) + { + if (melon == null) + return; - try - { - object info = ModInformation[melon]; - ModInformation.Remove(melon); - ModInfoList.Remove(info); - } - catch (Exception ex) - { - MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); - } - } + if (!ModInformation.ContainsKey(melon)) + return; - private void ParseMelon(T melon) where T : MelonBase + try + { + var info = ModInformation[melon]; + ModInformation.Remove(melon); + ModInfoList.Remove(info); + } + catch (Exception ex) { + MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); + } + } - if (melon == null) - return; + private void ParseMelon(T melon) where T : MelonBase + { - if (ModInformation.ContainsKey(melon)) - return; + if (melon == null) + return; - try - { - object info = Activator.CreateInstance(modInfoType); + if (ModInformation.ContainsKey(melon)) + return; - name_field.SetValue(info, melon.Info.Name); - version_field.SetValue(info, melon.Info.Version); - author_field.SetValue(info, melon.Info.Author); - description_field.SetValue(info, melon.Info.DownloadLink); - isNetworkCompatible_field.SetValue(info, MelonUtils.PullAttributeFromAssembly(melon.MelonAssembly.Assembly) == null); + try + { + var info = Activator.CreateInstance(modInfoType); - ModInformation.Add(melon, info); - ModInfoList.Add(info); - } - catch (Exception ex) - { - MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); - } + name_field.SetValue(info, melon.Info.Name); + version_field.SetValue(info, melon.Info.Version); + author_field.SetValue(info, melon.Info.Author); + description_field.SetValue(info, melon.Info.DownloadLink); + isNetworkCompatible_field.SetValue(info, MelonUtils.PullAttributeFromAssembly(melon.MelonAssembly.Assembly) == null); + + ModInformation.Add(melon, info); + ModInfoList.Add(info); + } + catch (Exception ex) + { + MelonLogger.Error($"Demeo Integration has thrown an exception: {ex}"); } } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs b/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs index bb06d52a9..f01b712be 100644 --- a/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs +++ b/Dependencies/CompatibilityLayers/IPA/IPA/ModPrefs.cs @@ -1,5 +1,4 @@ -using System; -using MelonLoader; +using MelonLoader; #pragma warning disable IDE0130 // Namespace does not match folder structure namespace IllusionPlugin; @@ -9,45 +8,37 @@ public static class ModPrefs { public static string GetString(string section, string name, string defaultValue = "", bool autoSave = false) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, defaultValue); return entry.Value; } public static int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, defaultValue); return entry.Value; } public static float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, defaultValue); return entry.Value; } public static bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, defaultValue); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, defaultValue); return entry.Value; } @@ -55,45 +46,37 @@ public static bool GetBool(string section, string name, bool defaultValue = fals public static void SetFloat(string section, string name, float value) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, value); entry.Value = value; } public static void SetInt(string section, string name, int value) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, value); entry.Value = value; } public static void SetString(string section, string name, string value) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, value); entry.Value = value; } public static void SetBool(string section, string name, bool value) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - category = MelonPreferences.CreateCategory(section); - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - entry = category.CreateEntry(name, value); + var category = MelonPreferences.GetCategory(section); + category ??= MelonPreferences.CreateCategory(section); + var entry = category.GetEntry(name); + entry ??= category.CreateEntry(name, value); entry.Value = value; } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs b/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs index 45c3026b7..4be3cb46e 100644 --- a/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs +++ b/Dependencies/CompatibilityLayers/IPA/IPA/PluginManager.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; -using IllusionPlugin; +using IllusionPlugin; using MelonLoader.Utils; +using System.Collections.Generic; #pragma warning disable IDE0130 // Namespace does not match folder structure namespace IllusionInjector; @@ -8,7 +8,7 @@ namespace IllusionInjector; public static class PluginManager { - internal static List _Plugins = new List(); + internal static List _Plugins = []; public static IEnumerable Plugins { get => _Plugins; } public class AppInfo { diff --git a/Dependencies/CompatibilityLayers/IPA/IPAPluginWrapper.cs b/Dependencies/CompatibilityLayers/IPA/IPAPluginWrapper.cs index b98612372..af23f3613 100644 --- a/Dependencies/CompatibilityLayers/IPA/IPAPluginWrapper.cs +++ b/Dependencies/CompatibilityLayers/IPA/IPAPluginWrapper.cs @@ -1,22 +1,19 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using IllusionPlugin; -using IllusionInjector; +using IllusionPlugin; -namespace MelonLoader.CompatibilityLayers +namespace MelonLoader.CompatibilityLayers; + +internal class IPAPluginWrapper : MelonMod { - internal class IPAPluginWrapper : MelonMod - { - internal IPlugin pluginInstance; - public override void OnInitializeMelon() => pluginInstance.OnApplicationStart(); - public override void OnDeinitializeMelon() => pluginInstance.OnApplicationQuit(); - public override void OnSceneWasLoaded(int buildIndex, string sceneName) => pluginInstance.OnLevelWasLoaded(buildIndex); - public override void OnSceneWasInitialized(int buildIndex, string sceneName) => pluginInstance.OnLevelWasInitialized(buildIndex); - public override void OnUpdate() => pluginInstance.OnUpdate(); - public override void OnFixedUpdate() => pluginInstance.OnFixedUpdate(); - public override void OnLateUpdate() { if (pluginInstance is IEnhancedPlugin plugin) plugin.OnLateUpdate(); } - } + internal IPlugin pluginInstance; + public override void OnInitializeMelon() => pluginInstance.OnApplicationStart(); + public override void OnDeinitializeMelon() => pluginInstance.OnApplicationQuit(); + public override void OnSceneWasLoaded(int buildIndex, string sceneName) => pluginInstance.OnLevelWasLoaded(buildIndex); + public override void OnSceneWasInitialized(int buildIndex, string sceneName) => pluginInstance.OnLevelWasInitialized(buildIndex); + public override void OnUpdate() => pluginInstance.OnUpdate(); + public override void OnFixedUpdate() => pluginInstance.OnFixedUpdate(); + public override void OnLateUpdate() + { + if (pluginInstance is IEnhancedPlugin plugin) + plugin.OnLateUpdate(); + } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/IPA/Module.cs b/Dependencies/CompatibilityLayers/IPA/Module.cs index 87cb99a13..be9faaa32 100644 --- a/Dependencies/CompatibilityLayers/IPA/Module.cs +++ b/Dependencies/CompatibilityLayers/IPA/Module.cs @@ -1,91 +1,92 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using IllusionInjector; using IllusionPlugin; -using IllusionInjector; using MelonLoader.Modules; using MelonLoader.Resolver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; [assembly: MelonLoader.PatchShield] -namespace MelonLoader.CompatibilityLayers +namespace MelonLoader.CompatibilityLayers; + +internal class IPA_Module : MelonModule { - internal class IPA_Module : MelonModule + public override void OnInitialize() { - public override void OnInitialize() - { - // To-Do: - // Detect if IPA is already Installed - // Point AssemblyResolveInfo to already installed IPA Assembly - // Point GetResolverFromAssembly to Dummy MelonCompatibilityLayer.Resolver + // To-Do: + // Detect if IPA is already Installed + // Point AssemblyResolveInfo to already installed IPA Assembly + // Point GetResolverFromAssembly to Dummy MelonCompatibilityLayer.Resolver - string[] assembly_list = - { - "IllusionPlugin", - "IllusionInjector", - }; - Assembly base_assembly = typeof(IPA_Module).Assembly; - foreach (string assemblyName in assembly_list) - MelonAssemblyResolver.GetAssemblyResolveInfo(assemblyName).Override = base_assembly; + string[] assembly_list = + { + "IllusionPlugin", + "IllusionInjector", + }; + var base_assembly = typeof(IPA_Module).Assembly; + foreach (var assemblyName in assembly_list) + MelonAssemblyResolver.GetAssemblyResolveInfo(assemblyName).Override = base_assembly; - MelonAssembly.CustomMelonResolvers += Resolve; - } + MelonAssembly.CustomMelonResolvers += Resolve; + } - private ResolvedMelons Resolve(Assembly asm) - { - IEnumerable pluginTypes = asm.GetValidTypes(x => - { - Type[] interfaces = x.GetInterfaces(); - return (interfaces != null) && interfaces.Any() && interfaces.Contains(typeof(IPlugin)); // To-Do: Change to Type Reflection based on Setup - }); - if ((pluginTypes == null) || !pluginTypes.Any()) - return new ResolvedMelons(null, null); + private ResolvedMelons Resolve(Assembly asm) + { + var pluginTypes = asm.GetValidTypes(x => + { + var interfaces = x.GetInterfaces(); + return (interfaces != null) && interfaces.Any() && interfaces.Contains(typeof(IPlugin)); // To-Do: Change to Type Reflection based on Setup + }); + if ((pluginTypes == null) || !pluginTypes.Any()) + return new ResolvedMelons(null, null); - var melons = new List(); - var rotten = new List(); - foreach (var t in pluginTypes) - { - var mel = LoadPlugin(asm, t, out RottenMelon rm); - if (mel != null) - melons.Add(mel); - else - rotten.Add(rm); - } - return new ResolvedMelons(melons.ToArray(), rotten.ToArray()); - } + var melons = new List(); + var rotten = new List(); + foreach (var t in pluginTypes) + { + var mel = LoadPlugin(asm, t, out var rm); + if (mel != null) + melons.Add(mel); + else + rotten.Add(rm); + } + return new ResolvedMelons(melons.ToArray(), rotten.ToArray()); + } - private MelonBase LoadPlugin(Assembly asm, Type pluginType, out RottenMelon rottenMelon) - { - rottenMelon = null; - IPlugin pluginInstance; - try - { pluginInstance = Activator.CreateInstance(pluginType) as IPlugin; } - catch (Exception ex) - { - rottenMelon = new RottenMelon(pluginType, "Failed to create a new instance of the IPA Plugin.", ex); - return null; - } + private MelonBase LoadPlugin(Assembly asm, Type pluginType, out RottenMelon rottenMelon) + { + rottenMelon = null; + IPlugin pluginInstance; + try + { + pluginInstance = Activator.CreateInstance(pluginType) as IPlugin; + } + catch (Exception ex) + { + rottenMelon = new RottenMelon(pluginType, "Failed to create a new instance of the IPA Plugin.", ex); + return null; + } - MelonProcessAttribute[] processAttrs = null; - if (pluginInstance is IEnhancedPlugin enPl) - processAttrs = enPl.Filter?.Select(x => new MelonProcessAttribute(x)).ToArray(); + MelonProcessAttribute[] processAttrs = null; + if (pluginInstance is IEnhancedPlugin enPl) + processAttrs = enPl.Filter?.Select(x => new MelonProcessAttribute(x)).ToArray(); - string pluginName = pluginInstance.Name; - if (string.IsNullOrEmpty(pluginName)) - pluginName = pluginType.FullName; + var pluginName = pluginInstance.Name; + if (string.IsNullOrEmpty(pluginName)) + pluginName = pluginType.FullName; - string plugin_version = pluginInstance.Version; - if (string.IsNullOrEmpty(plugin_version)) - plugin_version = asm.GetName().Version.ToString(); - if (string.IsNullOrEmpty(plugin_version) || plugin_version.Equals("0.0.0.0")) - plugin_version = "1.0.0.0"; + var plugin_version = pluginInstance.Version; + if (string.IsNullOrEmpty(plugin_version)) + plugin_version = asm.GetName().Version.ToString(); + if (string.IsNullOrEmpty(plugin_version) || plugin_version.Equals("0.0.0.0")) + plugin_version = "1.0.0.0"; - var melon = MelonBase.CreateWrapper(pluginName, null, plugin_version, processes: processAttrs); + var melon = MelonBase.CreateWrapper(pluginName, null, plugin_version, processes: processAttrs); - melon.pluginInstance = pluginInstance; - PluginManager._Plugins.Add(pluginInstance); - return melon; - } - } + melon.pluginInstance = pluginInstance; + PluginManager._Plugins.Add(pluginInstance); + return melon; + } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs index 80c8d1ce0..cbee2ea5b 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs @@ -1,85 +1,87 @@ -using System; +using MelonLoader.Modules; +using MelonLoader.Resolver; +using ModHelper; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using MelonLoader.Modules; -using ModHelper; -using MelonLoader.Resolver; [assembly: MelonLoader.PatchShield] -namespace MelonLoader.CompatibilityLayers +namespace MelonLoader.CompatibilityLayers; + +internal class Muse_Dash_Mono_Module : MelonModule { - internal class Muse_Dash_Mono_Module : MelonModule + public override void OnInitialize() { - public override void OnInitialize() - { - // To-Do: - // Detect if MuseDashModLoader is already Installed - // Point AssemblyResolveInfo to already installed MuseDashModLoader Assembly - // Inject Custom Resolver + // To-Do: + // Detect if MuseDashModLoader is already Installed + // Point AssemblyResolveInfo to already installed MuseDashModLoader Assembly + // Inject Custom Resolver - string[] assembly_list = - { - "ModHelper", - "ModLoader", - }; - Assembly base_assembly = typeof(Muse_Dash_Mono_Module).Assembly; - foreach (string assemblyName in assembly_list) - MelonAssemblyResolver.GetAssemblyResolveInfo(assemblyName).Override = base_assembly; + string[] assembly_list = + { + "ModHelper", + "ModLoader", + }; + var base_assembly = typeof(Muse_Dash_Mono_Module).Assembly; + foreach (var assemblyName in assembly_list) + MelonAssemblyResolver.GetAssemblyResolveInfo(assemblyName).Override = base_assembly; - MelonAssembly.CustomMelonResolvers += Resolve; - } + MelonAssembly.CustomMelonResolvers += Resolve; + } - private ResolvedMelons Resolve(Assembly asm) + private ResolvedMelons Resolve(Assembly asm) + { + var modTypes = asm.GetValidTypes(x => { - IEnumerable modTypes = asm.GetValidTypes(x => - { - Type[] interfaces = x.GetInterfaces(); - return (interfaces != null) && interfaces.Any() && interfaces.Contains(typeof(IMod)); // To-Do: Change to Type Reflection based on Setup - }); - if ((modTypes == null) || !modTypes.Any()) - return new ResolvedMelons(null, null); + var interfaces = x.GetInterfaces(); + return (interfaces != null) && interfaces.Any() && interfaces.Contains(typeof(IMod)); // To-Do: Change to Type Reflection based on Setup + }); + if ((modTypes == null) || !modTypes.Any()) + return new ResolvedMelons(null, null); - var melons = new List(); - var rotten = new List(); - foreach (var t in modTypes) - { - var mel = LoadMod(asm, t, out RottenMelon rm); - if (mel != null) - melons.Add(mel); - else - rotten.Add(rm); - } - return new ResolvedMelons(melons.ToArray(), rotten.ToArray()); + var melons = new List(); + var rotten = new List(); + foreach (var t in modTypes) + { + var mel = LoadMod(asm, t, out var rm); + if (mel != null) + melons.Add(mel); + else + rotten.Add(rm); } + return new ResolvedMelons(melons.ToArray(), rotten.ToArray()); + } - private MelonBase LoadMod(Assembly asm, Type modType, out RottenMelon rottenMelon) - { - rottenMelon = null; + private MelonBase LoadMod(Assembly asm, Type modType, out RottenMelon rottenMelon) + { + rottenMelon = null; - IMod modInstance; - try { modInstance = Activator.CreateInstance(modType) as IMod; } - catch (Exception ex) - { - rottenMelon = new RottenMelon(modType, "Failed to create an instance of the MMDL Mod.", ex); - return null; - } + IMod modInstance; + try + { + modInstance = Activator.CreateInstance(modType) as IMod; + } + catch (Exception ex) + { + rottenMelon = new RottenMelon(modType, "Failed to create an instance of the MMDL Mod.", ex); + return null; + } - var modName = modInstance.Name; + var modName = modInstance.Name; - if (string.IsNullOrEmpty(modName)) - modName = modType.FullName; + if (string.IsNullOrEmpty(modName)) + modName = modType.FullName; - var modVersion = asm.GetName().Version.ToString(); - if (string.IsNullOrEmpty(modVersion) || modVersion.Equals("0.0.0.0")) - modVersion = "1.0.0.0"; + var modVersion = asm.GetName().Version.ToString(); + if (string.IsNullOrEmpty(modVersion) || modVersion.Equals("0.0.0.0")) + modVersion = "1.0.0.0"; - var melon = MelonBase.CreateWrapper(modName, null, modVersion); - melon.modInstance = modInstance; - ModLoader.ModLoader.mods.Add(modInstance); - ModLoader.ModLoader.LoadDependency(asm); - return melon; - } + var melon = MelonBase.CreateWrapper(modName, null, modVersion); + melon.modInstance = modInstance; + ModLoader.ModLoader.mods.Add(modInstance); + ModLoader.ModLoader.LoadDependency(asm); + return melon; } } \ No newline at end of file diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs index 29e85fa0d..41a4ad960 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; -using System.Reflection; using MelonLoader; using ModHelper; +using System.Collections.Generic; +using System.Reflection; #pragma warning disable IDE0130 // Namespace does not match folder structure namespace ModLoader; @@ -9,17 +9,17 @@ namespace ModLoader; public class ModLoader { - internal static List mods = new List(); - internal static Dictionary depends = new Dictionary(); - + internal static List mods = []; + internal static Dictionary depends = []; + public static void LoadDependency(Assembly assembly) { - foreach (string dependStr in assembly.GetManifestResourceNames()) + foreach (var dependStr in assembly.GetManifestResourceNames()) { - string filter = $"{assembly.GetName().Name}.Depends."; + var filter = $"{assembly.GetName().Name}.Depends."; if (dependStr.StartsWith(filter) && dependStr.EndsWith(".dll")) { - string dependName = dependStr.Remove(dependStr.LastIndexOf(".dll")).Remove(0, filter.Length); + var dependName = dependStr.Remove(dependStr.LastIndexOf(".dll")).Remove(0, filter.Length); if (depends.ContainsKey(dependName)) { MelonLogger.Error($"Dependency conflict: {dependName} First at: {depends[dependName].GetName().Name}"); @@ -29,7 +29,7 @@ public static void LoadDependency(Assembly assembly) Assembly dependAssembly; using (var stream = assembly.GetManifestResourceStream(dependStr)) { - byte[] buffer = new byte[stream.Length]; + var buffer = new byte[stream.Length]; stream.Read(buffer, 0, buffer.Length); dependAssembly = Assembly.Load(buffer); } diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs index ffa094d01..3c87b613c 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLogger.cs @@ -1,5 +1,5 @@ -using System.Diagnostics; using MelonLoader; +using System.Diagnostics; #pragma warning disable IDE0130 // Namespace does not match folder structure namespace ModHelper; diff --git a/Dependencies/CompatibilityLayers/Stress_Level_Zero_Il2Cpp/Module.cs b/Dependencies/CompatibilityLayers/Stress_Level_Zero_Il2Cpp/Module.cs index a71f73b76..9d3f6c5d2 100644 --- a/Dependencies/CompatibilityLayers/Stress_Level_Zero_Il2Cpp/Module.cs +++ b/Dependencies/CompatibilityLayers/Stress_Level_Zero_Il2Cpp/Module.cs @@ -3,69 +3,68 @@ [assembly: MelonLoader.PatchShield] -namespace MelonLoader.CompatibilityLayers +namespace MelonLoader.CompatibilityLayers; + +internal class SLZ_Module : MelonModule { - internal class SLZ_Module : MelonModule - { - private static bool HasGotLoadingSceneIndex = false; - private static int LoadingSceneIndex = -9; + private static bool HasGotLoadingSceneIndex = false; + private static int LoadingSceneIndex = -9; - private static Dictionary CompatibleGames = new Dictionary - { - ["BONELAB"] = 0, - ["BONEWORKS"] = 0 - }; + private static readonly Dictionary CompatibleGames = new() + { + ["BONELAB"] = 0, + ["BONEWORKS"] = 0 + }; - public override void OnInitialize() - { - if (!CompatibleGames.ContainsKey(InternalUtils.UnityInformationHandler.GameName)) - return; + public override void OnInitialize() + { + if (!CompatibleGames.ContainsKey(InternalUtils.UnityInformationHandler.GameName)) + return; - MelonEvents.OnSceneWasLoaded.Subscribe(OnSceneLoad, int.MinValue); - MelonEvents.OnSceneWasInitialized.Subscribe(OnSceneInit, int.MinValue); - } + MelonEvents.OnSceneWasLoaded.Subscribe(OnSceneLoad, int.MinValue); + MelonEvents.OnSceneWasInitialized.Subscribe(OnSceneInit, int.MinValue); + } - private static void OnSceneLoad(int buildIndex, string name) + private static void OnSceneLoad(int buildIndex, string name) + { + if (HasGotLoadingSceneIndex) { - if (HasGotLoadingSceneIndex) - { - if (buildIndex == LoadingSceneIndex) - PreSceneEvent(); - return; - } + if (buildIndex == LoadingSceneIndex) + PreSceneEvent(); + return; + } - if (buildIndex == 0) - return; + if (buildIndex == 0) + return; - HasGotLoadingSceneIndex = true; - LoadingSceneIndex = buildIndex; - PreSceneEvent(); - } + HasGotLoadingSceneIndex = true; + LoadingSceneIndex = buildIndex; + PreSceneEvent(); + } - private static void OnSceneInit(int buildIndex, string name) - { - if (!HasGotLoadingSceneIndex - || (buildIndex != LoadingSceneIndex)) - return; + private static void OnSceneInit(int buildIndex, string name) + { + if (!HasGotLoadingSceneIndex + || (buildIndex != LoadingSceneIndex)) + return; - PostSceneEvent(); - MelonBase.SendMessageAll("OnLoadingScreen"); - MelonBase.SendMessageAll($"{InternalUtils.UnityInformationHandler.GameName}_OnLoadingScreen"); - } + PostSceneEvent(); + MelonBase.SendMessageAll("OnLoadingScreen"); + MelonBase.SendMessageAll($"{InternalUtils.UnityInformationHandler.GameName}_OnLoadingScreen"); + } - private static MelonEvent.MelonEventSubscriber[] SceneInitBackup; - private static void PreSceneEvent() - { - SceneInitBackup = MelonEvents.OnSceneWasInitialized.GetSubscribers(); - MelonEvents.OnSceneWasInitialized.UnsubscribeAll(); - MelonEvents.OnSceneWasInitialized.Subscribe(OnSceneInit, int.MinValue); - } - private static void PostSceneEvent() - { - MelonEvents.OnSceneWasInitialized.UnsubscribeAll(); - foreach (var sub in SceneInitBackup) - MelonEvents.OnSceneWasInitialized.Subscribe(sub.del, sub.priority, sub.unsubscribeOnFirstInvocation); - SceneInitBackup = null; - } + private static MelonEvent.MelonEventSubscriber[] SceneInitBackup; + private static void PreSceneEvent() + { + SceneInitBackup = MelonEvents.OnSceneWasInitialized.GetSubscribers(); + MelonEvents.OnSceneWasInitialized.UnsubscribeAll(); + MelonEvents.OnSceneWasInitialized.Subscribe(OnSceneInit, int.MinValue); + } + private static void PostSceneEvent() + { + MelonEvents.OnSceneWasInitialized.UnsubscribeAll(); + foreach (var sub in SceneInitBackup) + MelonEvents.OnSceneWasInitialized.Subscribe(sub.del, sub.priority, sub.unsubscribeOnFirstInvocation); + SceneInitBackup = null; } } \ No newline at end of file diff --git a/Dependencies/Il2CppAssemblyGenerator/Config.cs b/Dependencies/Il2CppAssemblyGenerator/Config.cs index 2b45c6672..1f4d8fc0a 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Config.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Config.cs @@ -1,44 +1,43 @@ -using System; +using MelonLoader.Preferences; +using System; using System.Collections.Generic; using System.IO; -using MelonLoader.Preferences; -namespace MelonLoader.Il2CppAssemblyGenerator +namespace MelonLoader.Il2CppAssemblyGenerator; + +internal class Config { - internal class Config - { - private static string FilePath; - private static MelonPreferences_ReflectiveCategory Category; - internal static AssemblyGeneratorConfiguration Values; + private static string FilePath; + private static MelonPreferences_ReflectiveCategory Category; + internal static AssemblyGeneratorConfiguration Values; - internal static void Initialize() - { - FilePath = Path.Combine(Core.BasePath, "Config.cfg"); + internal static void Initialize() + { + FilePath = Path.Combine(Core.BasePath, "Config.cfg"); - Category = MelonPreferences.CreateCategory("Il2CppAssemblyGenerator"); - Category.SetFilePath(FilePath, printmsg: false); - Category.DestroyFileWatcher(); + Category = MelonPreferences.CreateCategory("Il2CppAssemblyGenerator"); + Category.SetFilePath(FilePath, printmsg: false); + Category.DestroyFileWatcher(); - Values = Category.GetValue(); + Values = Category.GetValue(); - if (!File.Exists(FilePath)) - Save(); - } + if (!File.Exists(FilePath)) + Save(); + } - internal static void Save() => Category.SaveToFile(false); + internal static void Save() => Category.SaveToFile(false); - public class AssemblyGeneratorConfiguration - { - public string GameAssemblyHash = null; - public string DeobfuscationRegex = null; - public string UnityVersion = "0.0.0.0"; - public string DumperVersion = "0.0.0.0"; - public string DumperSCRSVersion = "0.0.0.0"; + public class AssemblyGeneratorConfiguration + { + public string GameAssemblyHash = null; + public string DeobfuscationRegex = null; + public string UnityVersion = "0.0.0.0"; + public string DumperVersion = "0.0.0.0"; + public string DumperSCRSVersion = "0.0.0.0"; - [Obsolete("Il2CppAssemblyUnhollower support was discontinued.", true)] - public bool UseInterop = true; + [Obsolete("Il2CppAssemblyUnhollower support was discontinued.", true)] + public bool UseInterop = true; - public List OldFiles = new List(); - } + public List OldFiles = []; } } \ No newline at end of file diff --git a/Dependencies/Il2CppAssemblyGenerator/Core.cs b/Dependencies/Il2CppAssemblyGenerator/Core.cs index c81dfdee1..1519dab5d 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Core.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Core.cs @@ -1,169 +1,167 @@ -using System.IO; -using System.Net.Http; -using MelonLoader.Il2CppAssemblyGenerator.Packages; +using MelonLoader.Il2CppAssemblyGenerator.Packages; using MelonLoader.Il2CppAssemblyGenerator.Packages.Models; using MelonLoader.Modules; using MelonLoader.Properties; using MelonLoader.Utils; +using System.IO; +using System.Net.Http; + +namespace MelonLoader.Il2CppAssemblyGenerator; -namespace MelonLoader.Il2CppAssemblyGenerator +internal class Core : MelonModule { - internal class Core : MelonModule - { - internal static string BasePath = null; - internal static string GameAssemblyPath = null; - internal static string ManagedPath = null; + internal static string BasePath = null; + internal static string GameAssemblyPath = null; + internal static string ManagedPath = null; - internal static HttpClient webClient = null; + internal static HttpClient webClient = null; - internal static ExecutablePackage cpp2il = null; - internal static Cpp2IL_StrippedCodeRegSupport cpp2il_scrs = null; + internal static ExecutablePackage cpp2il = null; + internal static Cpp2IL_StrippedCodeRegSupport cpp2il_scrs = null; - internal static Packages.Il2CppInterop il2cppinterop = null; - internal static UnityDependencies unitydependencies = null; - internal static DeobfuscationMap deobfuscationMap = null; - internal static DeobfuscationRegex deobfuscationRegex = null; + internal static Packages.Il2CppInterop il2cppinterop = null; + internal static UnityDependencies unitydependencies = null; + internal static DeobfuscationMap deobfuscationMap = null; + internal static DeobfuscationRegex deobfuscationRegex = null; - internal static bool AssemblyGenerationNeeded = false; + internal static bool AssemblyGenerationNeeded = false; - internal static MelonLogger.Instance Logger; + internal static MelonLogger.Instance Logger; - public override void OnInitialize() - { - Logger = LoggerInstance; + public override void OnInitialize() + { + Logger = LoggerInstance; + + webClient = new(); + webClient.DefaultRequestHeaders.Add("User-Agent", $"{BuildInfo.Name} v{BuildInfo.Version}"); - webClient = new(); - webClient.DefaultRequestHeaders.Add("User-Agent", $"{BuildInfo.Name} v{BuildInfo.Version}"); + AssemblyGenerationNeeded = LoaderConfig.Current.UnityEngine.ForceRegeneration; - AssemblyGenerationNeeded = LoaderConfig.Current.UnityEngine.ForceRegeneration; + var gameAssemblyName = "GameAssembly"; + if (MelonUtils.IsUnix) + gameAssemblyName += ".so"; + if (MelonUtils.IsWindows) + gameAssemblyName += ".dll"; + if (MelonUtils.IsMac) + gameAssemblyName += ".dylib"; - string gameAssemblyName = "GameAssembly"; - if (MelonUtils.IsUnix) - gameAssemblyName += ".so"; - if (MelonUtils.IsWindows) - gameAssemblyName += ".dll"; - if (MelonUtils.IsMac) - gameAssemblyName += ".dylib"; + GameAssemblyPath = Path.Combine(MelonEnvironment.GameRootDirectory, gameAssemblyName); + ManagedPath = MelonEnvironment.MelonManagedDirectory; + BasePath = MelonEnvironment.Il2CppAssemblyGeneratorDirectory; + } - GameAssemblyPath = Path.Combine(MelonEnvironment.GameRootDirectory, gameAssemblyName); - ManagedPath = MelonEnvironment.MelonManagedDirectory; - BasePath = MelonEnvironment.Il2CppAssemblyGeneratorDirectory; + private static int Run() + { + Config.Initialize(); + + if (!LoaderConfig.Current.UnityEngine.ForceOfflineGeneration) + RemoteAPI.Contact(); + + var cpp2IL_netcore = new Cpp2IL(); + cpp2il = MelonUtils.IsWindows + && (cpp2IL_netcore.VersionSem < Cpp2IL.NetCoreMinVersion) + ? new Cpp2IL_NetFramework() + : cpp2IL_netcore; + cpp2il_scrs = new Cpp2IL_StrippedCodeRegSupport(cpp2il); + + il2cppinterop = new Packages.Il2CppInterop(); + unitydependencies = new UnityDependencies(); + deobfuscationMap = new DeobfuscationMap(); + deobfuscationRegex = new DeobfuscationRegex(); + + Logger.Msg($"Using Cpp2IL Version: {(string.IsNullOrEmpty(cpp2il.Version) ? "null" : cpp2il.Version)}"); + Logger.Msg($"Using Il2CppInterop Version = {(string.IsNullOrEmpty(il2cppinterop.Version) ? "null" : il2cppinterop.Version)}"); + Logger.Msg($"Using Unity Dependencies Version = {(string.IsNullOrEmpty(unitydependencies.Version) ? "null" : unitydependencies.Version)}"); + Logger.Msg($"Using Deobfuscation Regex = {(string.IsNullOrEmpty(deobfuscationRegex.Regex) ? "null" : deobfuscationRegex.Regex)}"); + + if (!cpp2il.Setup() + || !cpp2il_scrs.Setup() + || !il2cppinterop.Setup() + || !unitydependencies.Setup() + || !deobfuscationMap.Setup()) + return 1; + + deobfuscationRegex.Setup(); + + string CurrentGameAssemblyHash; + Logger.Msg("Checking GameAssembly..."); + MelonDebug.Msg($"Last GameAssembly Hash: {Config.Values.GameAssemblyHash}"); + MelonDebug.Msg($"Current GameAssembly Hash: {CurrentGameAssemblyHash = MelonUtils.ComputeSimpleSHA512Hash(GameAssemblyPath)}"); + + if (string.IsNullOrEmpty(Config.Values.GameAssemblyHash) + || !Config.Values.GameAssemblyHash.Equals(CurrentGameAssemblyHash)) + AssemblyGenerationNeeded = true; + + if (!AssemblyGenerationNeeded) + { + Logger.Msg("Assembly is up to date. No Generation Needed."); + return 0; } + Logger.Msg("Assembly Generation Needed!"); - private static int Run() + cpp2il.Cleanup(); + il2cppinterop.Cleanup(); + + if (!cpp2il.Execute()) { - Config.Initialize(); - - if (!LoaderConfig.Current.UnityEngine.ForceOfflineGeneration) - RemoteAPI.Contact(); - - Cpp2IL cpp2IL_netcore = new Cpp2IL(); - if (MelonUtils.IsWindows - && (cpp2IL_netcore.VersionSem < Cpp2IL.NetCoreMinVersion)) - cpp2il = new Cpp2IL_NetFramework(); - else - cpp2il = cpp2IL_netcore; - cpp2il_scrs = new Cpp2IL_StrippedCodeRegSupport(cpp2il); - - il2cppinterop = new Packages.Il2CppInterop(); - unitydependencies = new UnityDependencies(); - deobfuscationMap = new DeobfuscationMap(); - deobfuscationRegex = new DeobfuscationRegex(); - - Logger.Msg($"Using Cpp2IL Version: {(string.IsNullOrEmpty(cpp2il.Version) ? "null" : cpp2il.Version)}"); - Logger.Msg($"Using Il2CppInterop Version = {(string.IsNullOrEmpty(il2cppinterop.Version) ? "null" : il2cppinterop.Version)}"); - Logger.Msg($"Using Unity Dependencies Version = {(string.IsNullOrEmpty(unitydependencies.Version) ? "null" : unitydependencies.Version)}"); - Logger.Msg($"Using Deobfuscation Regex = {(string.IsNullOrEmpty(deobfuscationRegex.Regex) ? "null" : deobfuscationRegex.Regex)}"); - - if (!cpp2il.Setup() - || !cpp2il_scrs.Setup() - || !il2cppinterop.Setup() - || !unitydependencies.Setup() - || !deobfuscationMap.Setup()) - return 1; - - deobfuscationRegex.Setup(); - - string CurrentGameAssemblyHash; - Logger.Msg("Checking GameAssembly..."); - MelonDebug.Msg($"Last GameAssembly Hash: {Config.Values.GameAssemblyHash}"); - MelonDebug.Msg($"Current GameAssembly Hash: {CurrentGameAssemblyHash = MelonUtils.ComputeSimpleSHA512Hash(GameAssemblyPath)}"); - - if (string.IsNullOrEmpty(Config.Values.GameAssemblyHash) - || !Config.Values.GameAssemblyHash.Equals(CurrentGameAssemblyHash)) - AssemblyGenerationNeeded = true; - - if (!AssemblyGenerationNeeded) - { - Logger.Msg("Assembly is up to date. No Generation Needed."); - return 0; - } - Logger.Msg("Assembly Generation Needed!"); + cpp2il.Cleanup(); + return 1; + } + if (!il2cppinterop.Execute()) + { cpp2il.Cleanup(); il2cppinterop.Cleanup(); + return 1; + } - if (!cpp2il.Execute()) - { - cpp2il.Cleanup(); - return 1; - } - - if (!il2cppinterop.Execute()) - { - cpp2il.Cleanup(); - il2cppinterop.Cleanup(); - return 1; - } - - OldFiles_Cleanup(); - OldFiles_LAM(); + OldFiles_Cleanup(); + OldFiles_LAM(); - cpp2il.Cleanup(); - il2cppinterop.Cleanup(); + cpp2il.Cleanup(); + il2cppinterop.Cleanup(); - Logger.Msg("Assembly Generation Successful!"); - deobfuscationRegex.Save(); - Config.Values.GameAssemblyHash = CurrentGameAssemblyHash; - Config.Save(); + Logger.Msg("Assembly Generation Successful!"); + deobfuscationRegex.Save(); + Config.Values.GameAssemblyHash = CurrentGameAssemblyHash; + Config.Save(); - return 0; - } + return 0; + } - private static void OldFiles_Cleanup() + private static void OldFiles_Cleanup() + { + if (Config.Values.OldFiles.Count <= 0) + return; + for (var i = 0; i < Config.Values.OldFiles.Count; i++) { - if (Config.Values.OldFiles.Count <= 0) - return; - for (int i = 0; i < Config.Values.OldFiles.Count; i++) + var filename = Config.Values.OldFiles[i]; + var filepath = Path.Combine(MelonEnvironment.Il2CppAssembliesDirectory, filename); + if (File.Exists(filepath)) { - string filename = Config.Values.OldFiles[i]; - string filepath = Path.Combine(MelonEnvironment.Il2CppAssembliesDirectory, filename); - if (File.Exists(filepath)) - { - Logger.Msg("Deleting " + filename); - File.Delete(filepath); - } + Logger.Msg("Deleting " + filename); + File.Delete(filepath); } - Config.Values.OldFiles.Clear(); } + Config.Values.OldFiles.Clear(); + } - private static void OldFiles_LAM() + private static void OldFiles_LAM() + { + var filepathtbl = Directory.GetFiles(il2cppinterop.OutputFolder); + var il2CppAssembliesDirectory = MelonEnvironment.Il2CppAssembliesDirectory; + for (var i = 0; i < filepathtbl.Length; i++) { - string[] filepathtbl = Directory.GetFiles(il2cppinterop.OutputFolder); - string il2CppAssembliesDirectory = MelonEnvironment.Il2CppAssembliesDirectory; - for (int i = 0; i < filepathtbl.Length; i++) - { - string filepath = filepathtbl[i]; - string filename = Path.GetFileName(filepath); - Logger.Msg("Moving " + filename); - Config.Values.OldFiles.Add(filename); - string newfilepath = Path.Combine(il2CppAssembliesDirectory, filename); - if (File.Exists(newfilepath)) - File.Delete(newfilepath); - Directory.CreateDirectory(il2CppAssembliesDirectory); - File.Move(filepath, newfilepath); - } - Config.Save(); + var filepath = filepathtbl[i]; + var filename = Path.GetFileName(filepath); + Logger.Msg("Moving " + filename); + Config.Values.OldFiles.Add(filename); + var newfilepath = Path.Combine(il2CppAssembliesDirectory, filename); + if (File.Exists(newfilepath)) + File.Delete(newfilepath); + Directory.CreateDirectory(il2CppAssembliesDirectory); + File.Move(filepath, newfilepath); } + Config.Save(); } } \ No newline at end of file diff --git a/Dependencies/Il2CppAssemblyGenerator/Extensions.cs b/Dependencies/Il2CppAssemblyGenerator/Extensions.cs index 6eb549cc0..2643fff07 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Extensions.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Extensions.cs @@ -10,5 +10,5 @@ public static void DownloadFile(this HttpClient client, string url, string dest) using var dlStream = client.GetStreamAsync(url).Result; using var fileStream = File.Open(dest, FileMode.Create, FileAccess.Write); dlStream.CopyTo(fileStream); - } + } } \ No newline at end of file diff --git a/Dependencies/Il2CppAssemblyGenerator/FileHandler.cs b/Dependencies/Il2CppAssemblyGenerator/FileHandler.cs index 1066295bc..5fdc5e5c2 100644 --- a/Dependencies/Il2CppAssemblyGenerator/FileHandler.cs +++ b/Dependencies/Il2CppAssemblyGenerator/FileHandler.cs @@ -2,129 +2,134 @@ using System.IO; using System.IO.Compression; -namespace MelonLoader.Il2CppAssemblyGenerator +namespace MelonLoader.Il2CppAssemblyGenerator; + +internal static class FileHandler { - internal static class FileHandler + internal static bool Download(string url, string destination) { - internal static bool Download(string url, string destination) + if (string.IsNullOrEmpty(url)) { - if (string.IsNullOrEmpty(url)) - { - Core.Logger.Error($"url cannot be Null or Empty!"); - return false; - } + Core.Logger.Error($"url cannot be Null or Empty!"); + return false; + } - if (string.IsNullOrEmpty(destination)) - { - Core.Logger.Error($"destination cannot be Null or Empty!"); - return false; - } + if (string.IsNullOrEmpty(destination)) + { + Core.Logger.Error($"destination cannot be Null or Empty!"); + return false; + } + + if (File.Exists(destination)) + File.Delete(destination); + + Core.Logger.Msg($"Downloading {url} to {destination}"); + try + { + Core.webClient.DownloadFile(url, destination); + } + catch (Exception ex) + { + Core.Logger.Error(ex.ToString()); if (File.Exists(destination)) File.Delete(destination); - Core.Logger.Msg($"Downloading {url} to {destination}"); - try { Core.webClient.DownloadFile(url, destination); } - catch (Exception ex) - { - Core.Logger.Error(ex.ToString()); - - if (File.Exists(destination)) - File.Delete(destination); + return false; + } - return false; - } + return true; + } - return true; + internal static bool Process(string filepath, string destination, string targetName = null) + { + if (string.IsNullOrEmpty(filepath)) + { + Core.Logger.Error($"filepath cannot be Null or Empty!"); + return false; } - internal static bool Process(string filepath, string destination, string targetName = null) + if (string.IsNullOrEmpty(destination)) { - if (string.IsNullOrEmpty(filepath)) - { - Core.Logger.Error($"filepath cannot be Null or Empty!"); - return false; - } - - if (string.IsNullOrEmpty(destination)) - { - Core.Logger.Error($"destination cannot be Null or Empty!"); - return false; - } + Core.Logger.Error($"destination cannot be Null or Empty!"); + return false; + } - if (filepath.Equals(destination)) - return true; + if (filepath.Equals(destination)) + return true; - if (!File.Exists(filepath)) - { - Core.Logger.Error($"{filepath} does not Exist!"); - return false; - } + if (!File.Exists(filepath)) + { + Core.Logger.Error($"{filepath} does not Exist!"); + return false; + } - if (Path.HasExtension(destination)) - { - if (File.Exists(destination)) - File.Delete(destination); - } - else + if (Path.HasExtension(destination)) + { + if (File.Exists(destination)) + File.Delete(destination); + } + else + { + if (Directory.Exists(destination)) { - if (Directory.Exists(destination)) + Core.Logger.Msg($"Cleaning {destination}"); + foreach (var entry in Directory.EnumerateFileSystemEntries(destination)) { - Core.Logger.Msg($"Cleaning {destination}"); - foreach (var entry in Directory.EnumerateFileSystemEntries(destination)) - { - if (Directory.Exists(entry)) - Directory.Delete(entry, true); - else - File.Delete(entry); - } - } - else - { - Core.Logger.Msg($"Creating Directory {destination}"); - Directory.CreateDirectory(destination); + if (Directory.Exists(entry)) + Directory.Delete(entry, true); + else + File.Delete(entry); } } - - string filename = Path.GetFileName(filepath); - if (!filename.EndsWith(".zip")) + else { - Core.Logger.Msg($"Moving {filepath} to {destination}"); - - if (!string.IsNullOrEmpty(targetName)) - destination = Path.Combine(destination, targetName); - - File.Move(filepath, destination); - return true; + Core.Logger.Msg($"Creating Directory {destination}"); + Directory.CreateDirectory(destination); } + } - Core.Logger.Msg($"Extracting {filepath} to {destination}"); - try { ZipFile.ExtractToDirectory(filepath, destination); } - catch (Exception ex) - { - Core.Logger.Error(ex.ToString()); + var filename = Path.GetFileName(filepath); + if (!filename.EndsWith(".zip")) + { + Core.Logger.Msg($"Moving {filepath} to {destination}"); - if (File.Exists(filepath)) - File.Delete(filepath); + if (!string.IsNullOrEmpty(targetName)) + destination = Path.Combine(destination, targetName); - if (Directory.Exists(destination)) - { - foreach (var entry in Directory.EnumerateFileSystemEntries(destination)) - { - if (Directory.Exists(entry)) - Directory.Delete(entry, true); - else - File.Delete(entry); - } - } + File.Move(filepath, destination); + return true; + } - return false; - } + Core.Logger.Msg($"Extracting {filepath} to {destination}"); + try + { + ZipFile.ExtractToDirectory(filepath, destination); + } + catch (Exception ex) + { + Core.Logger.Error(ex.ToString()); if (File.Exists(filepath)) File.Delete(filepath); - return true; + if (Directory.Exists(destination)) + { + foreach (var entry in Directory.EnumerateFileSystemEntries(destination)) + { + if (Directory.Exists(entry)) + Directory.Delete(entry, true); + else + File.Delete(entry); + } + } + + return false; } + + if (File.Exists(filepath)) + File.Delete(filepath); + + return true; } } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL.cs index 4a5b1b347..fb5d2c756 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL.cs @@ -1,85 +1,84 @@ -using System.Collections.Generic; +using Semver; +using System.Collections.Generic; using System.Diagnostics; using System.IO; -using Semver; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages +namespace MelonLoader.Il2CppAssemblyGenerator.Packages; + +internal class Cpp2IL : Models.ExecutablePackage { - internal class Cpp2IL : Models.ExecutablePackage + internal static SemVersion NetCoreMinVersion = SemVersion.Parse("2022.1.0-pre-release.18"); + internal SemVersion VersionSem; + private readonly string BaseFolder; + + private static string ReleaseName => + MelonUtils.IsWindows ? "Windows" : MelonUtils.IsUnix ? "Linux" : "OSX"; + + internal Cpp2IL() { - internal static SemVersion NetCoreMinVersion = SemVersion.Parse("2022.1.0-pre-release.18"); - internal SemVersion VersionSem; - private string BaseFolder; - - private static string ReleaseName => - MelonUtils.IsWindows ? "Windows" : MelonUtils.IsUnix ? "Linux" : "OSX"; - - internal Cpp2IL() - { - Version = LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; + Version = LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; #if !DEBUG - if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) - Version = RemoteAPI.Info.ForceDumperVersion; + if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) + Version = RemoteAPI.Info.ForceDumperVersion; #endif - if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) - Version = $"2022.1.0-pre-release.19"; - VersionSem = SemVersion.Parse(Version); + if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) + Version = $"2022.1.0-pre-release.19"; + VersionSem = SemVersion.Parse(Version); - Name = nameof(Cpp2IL); - - var filename = Name; + Name = nameof(Cpp2IL); + + var filename = Name; #if WINDOWS - filename += ".exe"; + filename += ".exe"; #endif - BaseFolder = Path.Combine(Core.BasePath, Name); - Directory.CreateDirectory(BaseFolder); + BaseFolder = Path.Combine(Core.BasePath, Name); + Directory.CreateDirectory(BaseFolder); - FilePath = - ExeFilePath = - Destination = - Path.Combine(BaseFolder, filename); + FilePath = + ExeFilePath = + Destination = + Path.Combine(BaseFolder, filename); - OutputFolder = Path.Combine(BaseFolder, "cpp2il_out"); + OutputFolder = Path.Combine(BaseFolder, "cpp2il_out"); - URL = $"https://github.com/SamboyCoding/{Name}/releases/download/{Version}/{Name}-{Version}-{ReleaseName}"; + URL = $"https://github.com/SamboyCoding/{Name}/releases/download/{Version}/{Name}-{Version}-{ReleaseName}"; #if WINDOWS - URL += ".exe"; + URL += ".exe"; #endif - } + } - internal override bool ShouldSetup() - => string.IsNullOrEmpty(Config.Values.DumperVersion) - || !Config.Values.DumperVersion.Equals(Version); + internal override bool ShouldSetup() + => string.IsNullOrEmpty(Config.Values.DumperVersion) + || !Config.Values.DumperVersion.Equals(Version); - internal override void Cleanup() { } + internal override void Cleanup() { } - internal override void Save() - => Save(ref Config.Values.DumperVersion); + internal override void Save() + => Save(ref Config.Values.DumperVersion); - internal override bool Execute() - => Execute([ - MelonDebug.IsEnabled() ? "--verbose" : string.Empty, + internal override bool Execute() + => Execute([ + MelonDebug.IsEnabled() ? "--verbose" : string.Empty, - "--game-path", - "\"" + Path.GetDirectoryName(Core.GameAssemblyPath) + "\"", + "--game-path", + "\"" + Path.GetDirectoryName(Core.GameAssemblyPath) + "\"", - "--exe-name", - "\"" + Process.GetCurrentProcess().ProcessName + "\"", + "--exe-name", + "\"" + Process.GetCurrentProcess().ProcessName + "\"", - "--output-as", - "dummydll", + "--output-as", + "dummydll", - "--use-processor", - "attributeanalyzer", - "attributeinjector", - LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer ? "callanalyzer" : string.Empty, - LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector ? "nativemethoddetector" : string.Empty, - //"deobfmap", - //"stablenamer", + "--use-processor", + "attributeanalyzer", + "attributeinjector", + LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer ? "callanalyzer" : string.Empty, + LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector ? "nativemethoddetector" : string.Empty, + //"deobfmap", + //"stablenamer", - ], false, new Dictionary() { - {"NO_COLOR", "1"}, - }); - } + ], false, new Dictionary() { + {"NO_COLOR", "1"}, + }); } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_NetFramework.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_NetFramework.cs index 686b8508e..d055cf9c3 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_NetFramework.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_NetFramework.cs @@ -1,99 +1,90 @@ -using System.Collections.Generic; +using Semver; +using System.Collections.Generic; using System.Diagnostics; using System.IO; -using Semver; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages +namespace MelonLoader.Il2CppAssemblyGenerator.Packages; + +internal class Cpp2IL_NetFramework : Models.ExecutablePackage { - internal class Cpp2IL_NetFramework : Models.ExecutablePackage + private static string ReleaseName => "Windows-Netframework472"; + internal Cpp2IL_NetFramework() { - private static string ReleaseName => "Windows-Netframework472"; - internal Cpp2IL_NetFramework() - { - Version = LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; + Version = LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; #if !DEBUG - if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) - Version = RemoteAPI.Info.ForceDumperVersion; + if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) + Version = RemoteAPI.Info.ForceDumperVersion; #endif - if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) - Version = $"2022.1.0-pre-release.15"; - - Name = nameof(Cpp2IL); - Destination = Path.Combine(Core.BasePath, Name); - OutputFolder = Path.Combine(Destination, "cpp2il_out"); - - URL = $"https://github.com/SamboyCoding/{Name}/releases/download/{Version}/{Name}-{Version}-{ReleaseName}.zip"; - ExeFilePath = Path.Combine(Destination, $"{Name}.exe"); - FilePath = Path.Combine(Core.BasePath, $"{Name}_{Version}.zip"); - } - - internal override bool ShouldSetup() - => string.IsNullOrEmpty(Config.Values.DumperVersion) - || !Config.Values.DumperVersion.Equals(Version); - - internal override void Cleanup() { } - - internal override void Save() - => Save(ref Config.Values.DumperVersion); - - internal override bool Execute() - { - if (SemVersion.Parse(Version) <= SemVersion.Parse("2022.0.999")) - return ExecuteOld(); - return ExecuteNew(); - } - - private bool ExecuteNew() - { - if (Execute([ - MelonDebug.IsEnabled() ? "--verbose" : string.Empty, - - "--game-path", - "\"" + Path.GetDirectoryName(Core.GameAssemblyPath) + "\"", - - "--exe-name", - "\"" + Process.GetCurrentProcess().ProcessName + "\"", - - "--output-as", - "dummydll", - - "--use-processor", - "attributeanalyzer", - "attributeinjector", - LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer ? "callanalyzer" : string.Empty, - LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector ? "nativemethoddetector" : string.Empty, - //"deobfmap", - //"stablenamer", - - ], false, new Dictionary() { - {"NO_COLOR", "1"} - })) - return true; - - return false; - } - - private bool ExecuteOld() - { - if (Execute([ - MelonDebug.IsEnabled() ? "--verbose" : string.Empty, - - "--game-path", - "\"" + Path.GetDirectoryName(Core.GameAssemblyPath) + "\"", - - "--exe-name", - "\"" + Process.GetCurrentProcess().ProcessName + "\"", - - "--skip-analysis", - "--skip-metadata-txts", - "--disable-registration-prompts" - - ], false, new Dictionary() { - {"NO_COLOR", "1"} - })) - return true; - - return false; - } + if (string.IsNullOrEmpty(Version) || Version.Equals("0.0.0.0")) + Version = $"2022.1.0-pre-release.15"; + + Name = nameof(Cpp2IL); + Destination = Path.Combine(Core.BasePath, Name); + OutputFolder = Path.Combine(Destination, "cpp2il_out"); + + URL = $"https://github.com/SamboyCoding/{Name}/releases/download/{Version}/{Name}-{Version}-{ReleaseName}.zip"; + ExeFilePath = Path.Combine(Destination, $"{Name}.exe"); + FilePath = Path.Combine(Core.BasePath, $"{Name}_{Version}.zip"); + } + + internal override bool ShouldSetup() + => string.IsNullOrEmpty(Config.Values.DumperVersion) + || !Config.Values.DumperVersion.Equals(Version); + + internal override void Cleanup() { } + + internal override void Save() + => Save(ref Config.Values.DumperVersion); + + internal override bool Execute() + { + return SemVersion.Parse(Version) <= SemVersion.Parse("2022.0.999") ? ExecuteOld() : ExecuteNew(); + } + + private bool ExecuteNew() + { + return Execute([ + MelonDebug.IsEnabled() ? "--verbose" : string.Empty, + + "--game-path", + "\"" + Path.GetDirectoryName(Core.GameAssemblyPath) + "\"", + + "--exe-name", + "\"" + Process.GetCurrentProcess().ProcessName + "\"", + + "--output-as", + "dummydll", + + "--use-processor", + "attributeanalyzer", + "attributeinjector", + LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer ? "callanalyzer" : string.Empty, + LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector ? "nativemethoddetector" : string.Empty, + //"deobfmap", + //"stablenamer", + + ], false, new Dictionary() { + {"NO_COLOR", "1"} + }); + } + + private bool ExecuteOld() + { + return Execute([ + MelonDebug.IsEnabled() ? "--verbose" : string.Empty, + + "--game-path", + "\"" + Path.GetDirectoryName(Core.GameAssemblyPath) + "\"", + + "--exe-name", + "\"" + Process.GetCurrentProcess().ProcessName + "\"", + + "--skip-analysis", + "--skip-metadata-txts", + "--disable-registration-prompts" + + ], false, new Dictionary() { + {"NO_COLOR", "1"} + }); } } \ No newline at end of file diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_StrippedCodeRegSupport.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_StrippedCodeRegSupport.cs index 4492b30ba..32a3ab86e 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_StrippedCodeRegSupport.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/Cpp2IL_StrippedCodeRegSupport.cs @@ -2,52 +2,49 @@ using Semver; using System.IO; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages -{ - internal class Cpp2IL_StrippedCodeRegSupport : PackageBase - { - private static SemVersion MinVersion = SemVersion.Parse("2022.1.0-pre-release.19"); - private string _pluginsFolder; - private SemVersion VersionSem; - - internal Cpp2IL_StrippedCodeRegSupport(ExecutablePackage cpp2IL) - { - Name = $"{cpp2IL.Name}.Plugin.StrippedCodeRegSupport"; - Version = cpp2IL.Version; - VersionSem = SemVersion.Parse(Version); +namespace MelonLoader.Il2CppAssemblyGenerator.Packages; - string folderpath = Path.Combine(Core.BasePath, cpp2IL.Name); - string fileName = $"{Name}.dll"; - _pluginsFolder = Path.Combine(folderpath, "Plugins"); +internal class Cpp2IL_StrippedCodeRegSupport : PackageBase +{ + private static readonly SemVersion MinVersion = SemVersion.Parse("2022.1.0-pre-release.19"); + private readonly string _pluginsFolder; + private readonly SemVersion VersionSem; - FilePath = - Destination = - Path.Combine(_pluginsFolder, fileName); + internal Cpp2IL_StrippedCodeRegSupport(ExecutablePackage cpp2IL) + { + Name = $"{cpp2IL.Name}.Plugin.StrippedCodeRegSupport"; + Version = cpp2IL.Version; + VersionSem = SemVersion.Parse(Version); - URL = $"https://github.com/SamboyCoding/{cpp2IL.Name}/releases/download/{cpp2IL.Version}/{fileName}"; - } + var folderpath = Path.Combine(Core.BasePath, cpp2IL.Name); + var fileName = $"{Name}.dll"; + _pluginsFolder = Path.Combine(folderpath, "Plugins"); - internal override bool ShouldSetup() - { - if (VersionSem < MinVersion) - return false; + FilePath = + Destination = + Path.Combine(_pluginsFolder, fileName); - return string.IsNullOrEmpty(Config.Values.DumperSCRSVersion) - || !Config.Values.DumperSCRSVersion.Equals(Version); - } + URL = $"https://github.com/SamboyCoding/{cpp2IL.Name}/releases/download/{cpp2IL.Version}/{fileName}"; + } - internal override bool Setup() - { - if (VersionSem < Cpp2IL.NetCoreMinVersion) - return true; + internal override bool ShouldSetup() + { + return VersionSem >= MinVersion +&& (string.IsNullOrEmpty(Config.Values.DumperSCRSVersion) + || !Config.Values.DumperSCRSVersion.Equals(Version)); + } - if (!Directory.Exists(_pluginsFolder)) - Directory.CreateDirectory(_pluginsFolder); + internal override bool Setup() + { + if (VersionSem < Cpp2IL.NetCoreMinVersion) + return true; - return base.Setup(); - } + if (!Directory.Exists(_pluginsFolder)) + Directory.CreateDirectory(_pluginsFolder); - internal override void Save() - => Save(ref Config.Values.DumperSCRSVersion); + return base.Setup(); } + + internal override void Save() + => Save(ref Config.Values.DumperSCRSVersion); } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs index 9ce74b3a3..f9d2135d4 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs @@ -1,42 +1,41 @@ -using System; +using MelonLoader.Lemons.Cryptography; +using System; using System.IO; using System.Text; -using MelonLoader.Lemons.Cryptography; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages +namespace MelonLoader.Il2CppAssemblyGenerator.Packages; + +internal class DeobfuscationMap : Models.PackageBase { - internal class DeobfuscationMap : Models.PackageBase - { - private static LemonSHA512 lemonSHA512 = new LemonSHA512(); + private static readonly LemonSHA512 lemonSHA512 = new(); - internal DeobfuscationMap() - { - Name = nameof(DeobfuscationMap); - FilePath = Path.Combine(Core.BasePath, $"{Name}.csv.gz"); - Destination = FilePath; - URL = RemoteAPI.Info.MappingURL; - Version = RemoteAPI.Info.MappingFileSHA512; - } + internal DeobfuscationMap() + { + Name = nameof(DeobfuscationMap); + FilePath = Path.Combine(Core.BasePath, $"{Name}.csv.gz"); + Destination = FilePath; + URL = RemoteAPI.Info.MappingURL; + Version = RemoteAPI.Info.MappingFileSHA512; + } - internal override bool ShouldSetup() - { - if (string.IsNullOrEmpty(URL)) - return false; - if (string.IsNullOrEmpty(Version)) - return false; - else - { - if (!File.Exists(FilePath)) - return true; - byte[] hash = lemonSHA512.ComputeHash(File.ReadAllBytes(FilePath)); - StringBuilder hashstrb = new StringBuilder(128); - foreach (byte b in hash) - hashstrb.Append(b.ToString("x2")); - string hashstr = hashstrb.ToString(); - if (!hashstr.Equals(Version, StringComparison.OrdinalIgnoreCase)) - return true; - } + internal override bool ShouldSetup() + { + if (string.IsNullOrEmpty(URL)) return false; + if (string.IsNullOrEmpty(Version)) + return false; + else + { + if (!File.Exists(FilePath)) + return true; + var hash = lemonSHA512.ComputeHash(File.ReadAllBytes(FilePath)); + var hashstrb = new StringBuilder(128); + foreach (var b in hash) + hashstrb.Append(b.ToString("x2")); + var hashstr = hashstrb.ToString(); + if (!hashstr.Equals(Version, StringComparison.OrdinalIgnoreCase)) + return true; } + return false; } } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs index 493bf4c26..11294a616 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs @@ -1,45 +1,44 @@ -namespace MelonLoader.Il2CppAssemblyGenerator.Packages +namespace MelonLoader.Il2CppAssemblyGenerator.Packages; + +internal class DeobfuscationRegex { - internal class DeobfuscationRegex + internal string Regex = null; + + internal DeobfuscationRegex() { - internal string Regex = null; + Regex = LoaderConfig.Current.UnityEngine.ForceGeneratorRegex; + if (string.IsNullOrEmpty(Regex)) + Regex = RemoteAPI.Info.ObfuscationRegex; + } - internal DeobfuscationRegex() + internal void Setup() + { + if (string.IsNullOrEmpty(Regex)) { - Regex = LoaderConfig.Current.UnityEngine.ForceGeneratorRegex; - if (string.IsNullOrEmpty(Regex)) - Regex = RemoteAPI.Info.ObfuscationRegex; + if (!string.IsNullOrEmpty(Config.Values.DeobfuscationRegex)) + { + Core.AssemblyGenerationNeeded = true; + return; + } } - - internal void Setup() + else { - if (string.IsNullOrEmpty(Regex)) + if (string.IsNullOrEmpty(Config.Values.DeobfuscationRegex)) { - if (!string.IsNullOrEmpty(Config.Values.DeobfuscationRegex)) - { - Core.AssemblyGenerationNeeded = true; - return; - } + Core.AssemblyGenerationNeeded = true; + return; } - else + if (!Config.Values.DeobfuscationRegex.Equals(Regex)) { - if (string.IsNullOrEmpty(Config.Values.DeobfuscationRegex)) - { - Core.AssemblyGenerationNeeded = true; - return; - } - if (!Config.Values.DeobfuscationRegex.Equals(Regex)) - { - Core.AssemblyGenerationNeeded = true; - return; - } + Core.AssemblyGenerationNeeded = true; + return; } } + } - internal void Save() - { - Config.Values.DeobfuscationRegex = Regex; - Config.Save(); - } + internal void Save() + { + Config.Values.DeobfuscationRegex = Regex; + Config.Save(); } } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/Il2CppInterop.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/Il2CppInterop.cs index cbb6ca484..b2356b707 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/Il2CppInterop.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/Il2CppInterop.cs @@ -1,142 +1,145 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Serialized; using Il2CppInterop.Common; using Il2CppInterop.Generator; using Il2CppInterop.Generator.Runners; using Microsoft.Extensions.Logging; -using AsmResolver.DotNet; -using AsmResolver.DotNet.Serialized; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace MelonLoader.Il2CppAssemblyGenerator.Packages; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages +internal class Il2CppInterop : Models.ExecutablePackage { - internal class Il2CppInterop : Models.ExecutablePackage + internal Il2CppInterop() { - internal Il2CppInterop() - { - Version = typeof(Il2CppInteropGenerator).Assembly.CustomAttributes - .Where(x => x.AttributeType.Name == "AssemblyInformationalVersionAttribute") - .Select(x => x.ConstructorArguments[0].Value.ToString()) - .FirstOrDefault(); - - Name = nameof(Il2CppInterop); - Destination = Path.Combine(Core.BasePath, Name); - OutputFolder = Path.Combine(Destination, "Il2CppAssemblies"); - } - - internal override bool ShouldSetup() - => false; + Version = typeof(Il2CppInteropGenerator).Assembly.CustomAttributes + .Where(x => x.AttributeType.Name == "AssemblyInformationalVersionAttribute") + .Select(x => x.ConstructorArguments[0].Value.ToString()) + .FirstOrDefault(); + + Name = nameof(Il2CppInterop); + Destination = Path.Combine(Core.BasePath, Name); + OutputFolder = Path.Combine(Destination, "Il2CppAssemblies"); + } - internal override bool Execute() - { - Core.Logger.Msg("Reading dumped assemblies for interop generation..."); + internal override bool ShouldSetup() + => false; - var resolver = new InteropResolver(); - var inputAssemblies = Directory.GetFiles(Core.cpp2il.OutputFolder) - .Where(f => f.EndsWith(".dll")) - .Select(f => ModuleDefinition.FromFile(f, new ModuleReaderParameters() { ModuleResolver = resolver })) - .Select(f => { resolver.Add(f); return f; }) - .Select(f => f.Assembly) - .ToList(); + internal override bool Execute() + { + Core.Logger.Msg("Reading dumped assemblies for interop generation..."); - var opts = new GeneratorOptions() - { - GameAssemblyPath = Core.GameAssemblyPath, - Source = inputAssemblies, - OutputDir = OutputFolder, - UnityBaseLibsDir = Core.unitydependencies.Destination, - ObfuscatedNamesRegex = string.IsNullOrEmpty(Core.deobfuscationRegex.Regex) ? null : new Regex(Core.deobfuscationRegex.Regex), - Parallel = true, - Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptOut, - }; - - //Inform cecil of the unity base libs - var trusted = (string)AppDomain.CurrentDomain.GetData("TRUSTED_PLATFORM_ASSEMBLIES"); - var allUnityDlls = string.Join(Path.PathSeparator, Directory.GetFiles(Core.unitydependencies.Destination, "*.dll", SearchOption.TopDirectoryOnly)); - // var allDumpedDlls = string.Join(Path.PathSeparator, Directory.GetFiles(Core.dumper.OutputFolder, "*.dll", SearchOption.TopDirectoryOnly)); - AppDomain.CurrentDomain.SetData("TRUSTED_PLATFORM_ASSEMBLIES", trusted + Path.PathSeparator + allUnityDlls); - - if (!string.IsNullOrEmpty(Core.deobfuscationMap.Version)) + var resolver = new InteropResolver(); + var inputAssemblies = Directory.GetFiles(Core.cpp2il.OutputFolder) + .Where(f => f.EndsWith(".dll")) + .Select(f => ModuleDefinition.FromFile(f, new ModuleReaderParameters() { ModuleResolver = resolver })) + .Select(f => { - Core.Logger.Msg("Loading Deobfuscation Map..."); - opts.ReadRenameMap(Core.deobfuscationMap.Destination); - } + resolver.Add(f); + return f; + }) + .Select(f => f.Assembly) + .ToList(); + + var opts = new GeneratorOptions() + { + GameAssemblyPath = Core.GameAssemblyPath, + Source = inputAssemblies, + OutputDir = OutputFolder, + UnityBaseLibsDir = Core.unitydependencies.Destination, + ObfuscatedNamesRegex = string.IsNullOrEmpty(Core.deobfuscationRegex.Regex) ? null : new Regex(Core.deobfuscationRegex.Regex), + Parallel = true, + Il2CppPrefixMode = GeneratorOptions.PrefixMode.OptOut, + }; + + //Inform cecil of the unity base libs + var trusted = (string)AppDomain.CurrentDomain.GetData("TRUSTED_PLATFORM_ASSEMBLIES"); + var allUnityDlls = string.Join(Path.PathSeparator, Directory.GetFiles(Core.unitydependencies.Destination, "*.dll", SearchOption.TopDirectoryOnly)); + // var allDumpedDlls = string.Join(Path.PathSeparator, Directory.GetFiles(Core.dumper.OutputFolder, "*.dll", SearchOption.TopDirectoryOnly)); + AppDomain.CurrentDomain.SetData("TRUSTED_PLATFORM_ASSEMBLIES", trusted + Path.PathSeparator + allUnityDlls); + + if (!string.IsNullOrEmpty(Core.deobfuscationMap.Version)) + { + Core.Logger.Msg("Loading Deobfuscation Map..."); + opts.ReadRenameMap(Core.deobfuscationMap.Destination); + } - Core.Logger.Msg("Generating Interop Assemblies..."); + Core.Logger.Msg("Generating Interop Assemblies..."); #if !DEBUG - try + try #endif - { - Il2CppInteropGenerator.Create(opts) - .AddLogger(new InteropLogger()) - .AddInteropAssemblyGenerator() - .Run(); - } + { + Il2CppInteropGenerator.Create(opts) + .AddLogger(new InteropLogger()) + .AddInteropAssemblyGenerator() + .Run(); + } #if !DEBUG - catch (Exception e) - { - Core.Logger.Error("Error Generating Interop Assemblies!", e); - return false; - } + catch (Exception e) + { + Core.Logger.Error("Error Generating Interop Assemblies!", e); + return false; + } #endif - Core.Logger.Msg("Cleaning up..."); - AppDomain.CurrentDomain.SetData("TRUSTED_PLATFORM_ASSEMBLIES", trusted); - //inputAssemblies.ForEach(a => a.Dispose()); + Core.Logger.Msg("Cleaning up..."); + AppDomain.CurrentDomain.SetData("TRUSTED_PLATFORM_ASSEMBLIES", trusted); + //inputAssemblies.ForEach(a => a.Dispose()); - Core.Logger.Msg("Interop Generation Complete!"); - return true; - } + Core.Logger.Msg("Interop Generation Complete!"); + return true; } - - internal class InteropLogger : ILogger +} + +internal class InteropLogger : ILogger +{ + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + if (logLevel is LogLevel.Debug or LogLevel.Trace) { - if (logLevel is LogLevel.Debug or LogLevel.Trace) - { - MelonDebug.Msg(formatter(state, exception)); - return; - } - - Core.Logger.Msg(formatter(state, exception)); + MelonDebug.Msg(formatter(state, exception)); + return; } - public bool IsEnabled(LogLevel logLevel) - { - return logLevel switch - { - LogLevel.Debug or LogLevel.Trace => MelonDebug.IsEnabled(), - _ => true - }; - } + Core.Logger.Msg(formatter(state, exception)); + } - public IDisposable BeginScope(TState state) + public bool IsEnabled(LogLevel logLevel) + { + return logLevel switch { - throw new NotImplementedException(); - } + LogLevel.Debug or LogLevel.Trace => MelonDebug.IsEnabled(), + _ => true + }; } - internal class InteropResolver : INetModuleResolver + public IDisposable BeginScope(TState state) { - private readonly Dictionary _cache = new(); - - public void Dispose() - { - _cache.Clear(); - } - - internal void Add(ModuleDefinition module) - { - _cache[module.Name] = module; - } + throw new NotImplementedException(); + } +} - public ModuleDefinition Resolve(string name) - { - return _cache.GetValueOrDefault(name); - } +internal class InteropResolver : INetModuleResolver +{ + private readonly Dictionary _cache = []; + + public void Dispose() + { + _cache.Clear(); + } + + internal void Add(ModuleDefinition module) + { + _cache[module.Name] = module; + } + + public ModuleDefinition Resolve(string name) + { + return _cache.GetValueOrDefault(name); } } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/Models/ExecutablePackage.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/Models/ExecutablePackage.cs index aaa2ad8be..769ffd397 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/Models/ExecutablePackage.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/Models/ExecutablePackage.cs @@ -3,131 +3,144 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages.Models +namespace MelonLoader.Il2CppAssemblyGenerator.Packages.Models; + +internal class ExecutablePackage : PackageBase { - internal class ExecutablePackage : PackageBase + internal static AutoResetEvent ResetEvent_Output; + internal static AutoResetEvent ResetEvent_Error; + internal string OutputFolder; + internal string ExeFilePath; + + internal virtual void Cleanup() + { + if (!Directory.Exists(OutputFolder)) + return; + Directory.Delete(OutputFolder, true); + } + + internal virtual bool Execute() => true; + internal bool Execute(string[] args, bool parenthesize_args = true, Dictionary environment = null) { - internal static AutoResetEvent ResetEvent_Output; - internal static AutoResetEvent ResetEvent_Error; - internal string OutputFolder; - internal string ExeFilePath; + Cleanup(); - internal virtual void Cleanup() + if (!File.Exists(ExeFilePath)) { - if (!Directory.Exists(OutputFolder)) - return; - Directory.Delete(OutputFolder, true); + Core.Logger.Error($"{ExeFilePath} does not Exist!"); + ThrowInternalFailure($"Failed to Execute {Name}!"); + return false; } - internal virtual bool Execute() => true; - internal bool Execute(string[] args, bool parenthesize_args = true, Dictionary environment = null) + Core.Logger.Msg($"Executing {Name}..."); + try { - Cleanup(); +#if LINUX + // Make the file executable on Unix + chmod(ExeFilePath, S_IRUSR | S_IXUSR | S_IWUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); +#endif - if (!File.Exists(ExeFilePath)) - { - Core.Logger.Error($"{ExeFilePath} does not Exist!"); - ThrowInternalFailure($"Failed to Execute {Name}!"); - return false; - } + var tempFolder = Path.Combine(Path.GetDirectoryName(ExeFilePath), + $"{Path.GetFileNameWithoutExtension(ExeFilePath)}_Temp"); + if (!Directory.Exists(tempFolder)) + Directory.CreateDirectory(tempFolder); + + ResetEvent_Output = new AutoResetEvent(false); + ResetEvent_Error = new AutoResetEvent(false); - Core.Logger.Msg($"Executing {Name}..."); - try + var processStartInfo = new ProcessStartInfo(ExeFilePath, parenthesize_args + ? + string.Join(" ", args.Where(s => !string.IsNullOrEmpty(s)).Select(it => "\"" + Regex.Replace(it, @"(\\+)$", @"$1$1") + "\"")) + : + string.Join(" ", args.Where(s => !string.IsNullOrEmpty(s)).Select(it => Regex.Replace(it, @"(\\+)$", @"$1$1")))) { -#if LINUX - // Make the file executable on Unix - chmod(ExeFilePath, S_IRUSR | S_IXUSR | S_IWUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); -#endif - - string tempFolder = Path.Combine(Path.GetDirectoryName(ExeFilePath), - $"{Path.GetFileNameWithoutExtension(ExeFilePath)}_Temp"); - if (!Directory.Exists(tempFolder)) - Directory.CreateDirectory(tempFolder); - - ResetEvent_Output = new AutoResetEvent(false); - ResetEvent_Error = new AutoResetEvent(false); - - ProcessStartInfo processStartInfo = new ProcessStartInfo(ExeFilePath, parenthesize_args - ? - string.Join(" ", args.Where(s => !string.IsNullOrEmpty(s)).Select(it => "\"" + Regex.Replace(it, @"(\\+)$", @"$1$1") + "\"")) - : - string.Join(" ", args.Where(s => !string.IsNullOrEmpty(s)).Select(it => Regex.Replace(it, @"(\\+)$", @"$1$1")))); - - processStartInfo.UseShellExecute = false; - processStartInfo.RedirectStandardOutput = true; - processStartInfo.RedirectStandardError = true; - processStartInfo.CreateNoWindow = true; - processStartInfo.WorkingDirectory = Path.GetDirectoryName(ExeFilePath)!; - - if (environment != null) - { - processStartInfo.EnvironmentVariables["DOTNET_BUNDLE_EXTRACT_BASE_DIR"] = tempFolder; - foreach (var kvp in environment) - processStartInfo.EnvironmentVariables[kvp.Key] = kvp.Value; - } - - Core.Logger.Msg("\"" + ExeFilePath + "\" " + processStartInfo.Arguments); - - Process process = new Process(); - process.StartInfo = processStartInfo; - process.OutputDataReceived += OutputStream; - process.ErrorDataReceived += ErrorStream; - process.Start(); - - SetProcessId(process.Id); - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - process.WaitForExit(); - ResetEvent_Output.WaitOne(); - ResetEvent_Error.WaitOne(); - - SetProcessId(0); - - if (Directory.Exists(tempFolder)) - Directory.Delete(tempFolder, true); - - return process.ExitCode == 0; - } - catch (Exception ex) + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WorkingDirectory = Path.GetDirectoryName(ExeFilePath)! + }; + + if (environment != null) { - Core.Logger.Error(ex.ToString()); - ThrowInternalFailure($"Failed to Execute {Name}!"); + processStartInfo.EnvironmentVariables["DOTNET_BUNDLE_EXTRACT_BASE_DIR"] = tempFolder; + foreach (var kvp in environment) + processStartInfo.EnvironmentVariables[kvp.Key] = kvp.Value; } - return false; - } + Core.Logger.Msg("\"" + ExeFilePath + "\" " + processStartInfo.Arguments); + + var process = new Process + { + StartInfo = processStartInfo + }; + process.OutputDataReceived += OutputStream; + process.ErrorDataReceived += ErrorStream; + process.Start(); - private static void OutputStream(object sender, DataReceivedEventArgs e) { if (e.Data == null) ResetEvent_Output.Set(); else Core.Logger.Msg(e.Data); } - private static void ErrorStream(object sender, DataReceivedEventArgs e) { if (e.Data == null) ResetEvent_Error.Set(); else Core.Logger.Error(e.Data); } - - private static void SetProcessId(int id) + SetProcessId(process.Id); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + process.WaitForExit(); + ResetEvent_Output.WaitOne(); + ResetEvent_Error.WaitOne(); + + SetProcessId(0); + + if (Directory.Exists(tempFolder)) + Directory.Delete(tempFolder, true); + + return process.ExitCode == 0; + } + catch (Exception ex) { - //MelonLogger.Warning($"TODO: SetProcessId({id})"); + Core.Logger.Error(ex.ToString()); + ThrowInternalFailure($"Failed to Execute {Name}!"); } - + + return false; + } + + private static void OutputStream(object sender, DataReceivedEventArgs e) + { + if (e.Data == null) + ResetEvent_Output.Set(); + else + Core.Logger.Msg(e.Data); + } + private static void ErrorStream(object sender, DataReceivedEventArgs e) + { + if (e.Data == null) + ResetEvent_Error.Set(); + else + Core.Logger.Error(e.Data); + } + + private static void SetProcessId(int id) + { + //MelonLogger.Warning($"TODO: SetProcessId({id})"); + } + #if LINUX// user permissions - const int S_IRUSR = 0x100; - const int S_IWUSR = 0x80; - const int S_IXUSR = 0x40; - - // group permission - const int S_IRGRP = 0x20; - const int S_IWGRP = 0x10; - const int S_IXGRP = 0x8; - - // other permissions - const int S_IROTH = 0x4; - const int S_IWOTH = 0x2; - const int S_IXOTH = 0x1; - - [System.Runtime.InteropServices.DllImport("libc")] - private static extern int chmod(string pathname, int mode); + const int S_IRUSR = 0x100; + const int S_IWUSR = 0x80; + const int S_IXUSR = 0x40; + + // group permission + const int S_IRGRP = 0x20; + const int S_IWGRP = 0x10; + const int S_IXGRP = 0x8; + + // other permissions + const int S_IROTH = 0x4; + const int S_IWOTH = 0x2; + const int S_IXOTH = 0x1; + + [System.Runtime.InteropServices.DllImport("libc")] + private static extern int chmod(string pathname, int mode); #endif - } } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/Models/PackageBase.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/Models/PackageBase.cs index b5cbd232e..e02983bc6 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/Models/PackageBase.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/Models/PackageBase.cs @@ -1,62 +1,61 @@ using System.IO; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages.Models +namespace MelonLoader.Il2CppAssemblyGenerator.Packages.Models; + +internal class PackageBase { - internal class PackageBase - { - internal string Name; - internal string URL; - internal string FilePath; - internal string Destination; - internal string Version; + internal string Name; + internal string URL; + internal string FilePath; + internal string Destination; + internal string Version; - internal virtual bool ShouldSetup() => true; + internal virtual bool ShouldSetup() => true; - internal virtual bool OnProcess() - => FileHandler.Process(FilePath, Destination, MelonUtils.IsWindows ? null : Name); + internal virtual bool OnProcess() + => FileHandler.Process(FilePath, Destination, MelonUtils.IsWindows ? null : Name); - internal virtual bool Setup() - { - if (string.IsNullOrEmpty(Version) || string.IsNullOrEmpty(URL)) - return true; + internal virtual bool Setup() + { + if (string.IsNullOrEmpty(Version) || string.IsNullOrEmpty(URL)) + return true; - if (!ShouldSetup()) - { - Core.Logger.Msg($"{Name} is up to date."); - return true; - } + if (!ShouldSetup()) + { + Core.Logger.Msg($"{Name} is up to date."); + return true; + } - Core.AssemblyGenerationNeeded = true; + Core.AssemblyGenerationNeeded = true; - if (!LoaderConfig.Current.UnityEngine.ForceOfflineGeneration - && ((this is DeobfuscationMap) || !File.Exists(FilePath))) - { - Core.Logger.Msg($"Downloading {Name}..."); - if (!FileHandler.Download(URL, FilePath)) - { - ThrowInternalFailure($"Failed to Download {Name}!"); - return false; - } - } - - Core.Logger.Msg($"Processing {Name}..."); - if (!OnProcess()) + if (!LoaderConfig.Current.UnityEngine.ForceOfflineGeneration + && ((this is DeobfuscationMap) || !File.Exists(FilePath))) + { + Core.Logger.Msg($"Downloading {Name}..."); + if (!FileHandler.Download(URL, FilePath)) { - ThrowInternalFailure($"Failed to Process {Name}!"); + ThrowInternalFailure($"Failed to Download {Name}!"); return false; } - - Save(); - return true; } - internal virtual void Save() { } - internal void Save(ref string configPref) + Core.Logger.Msg($"Processing {Name}..."); + if (!OnProcess()) { - configPref = Version; - Config.Save(); + ThrowInternalFailure($"Failed to Process {Name}!"); + return false; } - - internal static void ThrowInternalFailure(string txt) => MelonLogger.ThrowInternalFailure(txt); + + Save(); + return true; } + + internal virtual void Save() { } + internal void Save(ref string configPref) + { + configPref = Version; + Config.Save(); + } + + internal static void ThrowInternalFailure(string txt) => MelonLogger.ThrowInternalFailure(txt); } diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/UnityDependencies.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/UnityDependencies.cs index 830b0bece..cbe5645f0 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Packages/UnityDependencies.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Packages/UnityDependencies.cs @@ -1,23 +1,22 @@ using System.IO; -namespace MelonLoader.Il2CppAssemblyGenerator.Packages +namespace MelonLoader.Il2CppAssemblyGenerator.Packages; + +internal class UnityDependencies : Models.PackageBase { - internal class UnityDependencies : Models.PackageBase + internal UnityDependencies() { - internal UnityDependencies() - { - Name = nameof(UnityDependencies); - Version = InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); - URL = $"https://github.com/LavaGang/MelonLoader.UnityDependencies/releases/download/{Version}/Managed.zip"; - Destination = Path.Combine(Core.BasePath, Name); - FilePath = Path.Combine(Core.BasePath, $"{Name}_{Version}.zip"); - } + Name = nameof(UnityDependencies); + Version = InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); + URL = $"https://github.com/LavaGang/MelonLoader.UnityDependencies/releases/download/{Version}/Managed.zip"; + Destination = Path.Combine(Core.BasePath, Name); + FilePath = Path.Combine(Core.BasePath, $"{Name}_{Version}.zip"); + } - internal override bool ShouldSetup() - => string.IsNullOrEmpty(Config.Values.UnityVersion) - || !Config.Values.UnityVersion.Equals(Version); + internal override bool ShouldSetup() + => string.IsNullOrEmpty(Config.Values.UnityVersion) + || !Config.Values.UnityVersion.Equals(Version); - internal override void Save() - => Save(ref Config.Values.UnityVersion); - } + internal override void Save() + => Save(ref Config.Values.UnityVersion); } diff --git a/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs b/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs index 2f64d3a25..00798b242 100644 --- a/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs +++ b/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs @@ -1,149 +1,150 @@ -using System; +using Semver; +using System; using System.Collections.Generic; using System.Net.Http; using System.Text.RegularExpressions; -using Semver; #pragma warning disable 0649 -namespace MelonLoader.Il2CppAssemblyGenerator +namespace MelonLoader.Il2CppAssemblyGenerator; + +internal static class RemoteAPI { - internal static class RemoteAPI + internal class InfoStruct { - internal class InfoStruct - { - internal string ForceDumperVersion = null; - internal string ObfuscationRegex = null; - internal string MappingURL = null; - internal string MappingFileSHA512 = null; - } - internal static InfoStruct Info = new InfoStruct(); + internal string ForceDumperVersion = null; + internal string ObfuscationRegex = null; + internal string MappingURL = null; + internal string MappingFileSHA512 = null; + } + internal static InfoStruct Info = new(); - private class HostInfo + private class HostInfo + { + internal string URL = null; + internal LemonFunc Func = null; + internal HostInfo(string url, LemonFunc func) { - internal string URL = null; - internal LemonFunc Func = null; - internal HostInfo(string url, LemonFunc func) - { - URL = url; - Func = func; - } + URL = url; + Func = func; } - private static List HostList = null; + } + private static readonly List HostList = null; - static RemoteAPI() - { - string gamename = Regex.Replace(InternalUtils.UnityInformationHandler.GameName, "[^a-zA-Z0-9_.]+", "-", RegexOptions.Compiled).ToLowerInvariant(); - - HostList = new List { - new HostInfo($"{DefaultHostInfo.Melon.API_URL}{gamename}", DefaultHostInfo.Melon.Contact), - new HostInfo($"{DefaultHostInfo.Melon.API_URL_1}{gamename}", DefaultHostInfo.Melon.Contact), - new HostInfo($"{DefaultHostInfo.Melon.API_URL_2}{gamename}", DefaultHostInfo.Melon.Contact), - new HostInfo($"{DefaultHostInfo.Melon.API_URL_SAMBOY}{gamename}", DefaultHostInfo.Melon.Contact), - new HostInfo($"{DefaultHostInfo.Melon.API_URL_DUBYADUDE}{gamename}", DefaultHostInfo.Melon.Contact), - }; - } + static RemoteAPI() + { + var gamename = Regex.Replace(InternalUtils.UnityInformationHandler.GameName, "[^a-zA-Z0-9_.]+", "-", RegexOptions.Compiled).ToLowerInvariant(); + + HostList = [ + new HostInfo($"{DefaultHostInfo.Melon.API_URL}{gamename}", DefaultHostInfo.Melon.Contact), + new HostInfo($"{DefaultHostInfo.Melon.API_URL_1}{gamename}", DefaultHostInfo.Melon.Contact), + new HostInfo($"{DefaultHostInfo.Melon.API_URL_2}{gamename}", DefaultHostInfo.Melon.Contact), + new HostInfo($"{DefaultHostInfo.Melon.API_URL_SAMBOY}{gamename}", DefaultHostInfo.Melon.Contact), + new HostInfo($"{DefaultHostInfo.Melon.API_URL_DUBYADUDE}{gamename}", DefaultHostInfo.Melon.Contact), + ]; + } - internal static void Contact() - { - Core.Logger.Msg("Contacting RemoteAPI..."); + internal static void Contact() + { + Core.Logger.Msg("Contacting RemoteAPI..."); - ContactHosts(); + ContactHosts(); - Core.Logger.Msg($"RemoteAPI.DumperVersion = {(string.IsNullOrEmpty(Info.ForceDumperVersion) ? "null" : Info.ForceDumperVersion)}"); - Core.Logger.Msg($"RemoteAPI.ObfuscationRegex = {(string.IsNullOrEmpty(Info.ObfuscationRegex) ? "null" : Info.ObfuscationRegex)}"); - Core.Logger.Msg($"RemoteAPI.MappingURL = {(string.IsNullOrEmpty(Info.MappingURL) ? "null" : Info.MappingURL)}"); - Core.Logger.Msg($"RemoteAPI.MappingFileSHA512 = {(string.IsNullOrEmpty(Info.MappingFileSHA512) ? "null" : Info.MappingFileSHA512)}"); - } + Core.Logger.Msg($"RemoteAPI.DumperVersion = {(string.IsNullOrEmpty(Info.ForceDumperVersion) ? "null" : Info.ForceDumperVersion)}"); + Core.Logger.Msg($"RemoteAPI.ObfuscationRegex = {(string.IsNullOrEmpty(Info.ObfuscationRegex) ? "null" : Info.ObfuscationRegex)}"); + Core.Logger.Msg($"RemoteAPI.MappingURL = {(string.IsNullOrEmpty(Info.MappingURL) ? "null" : Info.MappingURL)}"); + Core.Logger.Msg($"RemoteAPI.MappingFileSHA512 = {(string.IsNullOrEmpty(Info.MappingFileSHA512) ? "null" : Info.MappingFileSHA512)}"); + } - private static void ContactHosts() + private static void ContactHosts() + { + if ((HostList == null) || (HostList.Count <= 0)) + return; + foreach (var info in HostList) { - if ((HostList == null) || (HostList.Count <= 0)) - return; - foreach (HostInfo info in HostList) - { - if (string.IsNullOrEmpty(info.URL) || (info.Func == null)) - continue; + if (string.IsNullOrEmpty(info.URL) || (info.Func == null)) + continue; - MelonDebug.Msg($"ContactURL = {info.URL}"); + MelonDebug.Msg($"ContactURL = {info.URL}"); - string Response = null; - try - { - var result = Core.webClient.GetAsync(info.URL).Result; - result.EnsureSuccessStatusCode(); - Response = result.Content.ReadAsStringAsync().Result; - } - catch (Exception ex) + string Response; + try + { + var result = Core.webClient.GetAsync(info.URL).Result; + result.EnsureSuccessStatusCode(); + Response = result.Content.ReadAsStringAsync().Result; + } + catch (Exception ex) + { + if (ex is not HttpRequestException { StatusCode: { } } hre) { - if (ex is not HttpRequestException {StatusCode: {}} hre) - { - Core.Logger.Error($"Exception while Contacting RemoteAPI Host ({info.URL}): {ex}"); - continue; - } - - if (hre.StatusCode == System.Net.HttpStatusCode.NotFound) - { - Core.Logger.Msg($"Game Not Found on RemoteAPI Host ({info.URL})"); - break; - } - - Core.Logger.Error($"WebException ({hre.StatusCode}) while Contacting RemoteAPI Host ({info.URL}): {ex}"); + Core.Logger.Error($"Exception while Contacting RemoteAPI Host ({info.URL}): {ex}"); continue; } - var isResponseNull = string.IsNullOrEmpty(Response); - MelonDebug.Msg($"Response = {(isResponseNull ? "null" : Response) }"); - if (isResponseNull) + if (hre.StatusCode == System.Net.HttpStatusCode.NotFound) + { + Core.Logger.Msg($"Game Not Found on RemoteAPI Host ({info.URL})"); break; + } - InfoStruct returnInfo = info.Func(Response); - if (returnInfo == null) - continue; - - if (returnInfo.ForceDumperVersion != null && SemVersion.Parse(returnInfo.ForceDumperVersion) <= SemVersion.Parse("2022.0.2")) - returnInfo.ForceDumperVersion = null; + Core.Logger.Error($"WebException ({hre.StatusCode}) while Contacting RemoteAPI Host ({info.URL}): {ex}"); + continue; + } - Info = returnInfo; + var isResponseNull = string.IsNullOrEmpty(Response); + MelonDebug.Msg($"Response = {(isResponseNull ? "null" : Response)}"); + if (isResponseNull) break; - } + + var returnInfo = info.Func(Response); + if (returnInfo == null) + continue; + + if (returnInfo.ForceDumperVersion != null && SemVersion.Parse(returnInfo.ForceDumperVersion) <= SemVersion.Parse("2022.0.2")) + returnInfo.ForceDumperVersion = null; + + Info = returnInfo; + break; } + } - private class DefaultHostInfo + private class DefaultHostInfo + { + internal static class Melon { - internal static class Melon + internal static string API_VERSION = "v1"; + internal static string API_URL = $"https://api.melonloader.com/api/{API_VERSION}/game/"; + internal static string API_URL_1 = $"https://api-1.melonloader.com/api/{API_VERSION}/game/"; + internal static string API_URL_2 = $"https://api-2.melonloader.com/api/{API_VERSION}/game/"; + internal static string API_URL_SAMBOY = $"https://melon.samboy.dev/api/{API_VERSION}/game/"; + internal static string API_URL_DUBYADUDE = $"https://melon.dubyadu.de/api/{API_VERSION}/game/"; + + internal static InfoStruct Contact(string response_str) { - internal static string API_VERSION = "v1"; - internal static string API_URL = $"https://api.melonloader.com/api/{API_VERSION}/game/"; - internal static string API_URL_1 = $"https://api-1.melonloader.com/api/{API_VERSION}/game/"; - internal static string API_URL_2 = $"https://api-2.melonloader.com/api/{API_VERSION}/game/"; - internal static string API_URL_SAMBOY = $"https://melon.samboy.dev/api/{API_VERSION}/game/"; - internal static string API_URL_DUBYADUDE = $"https://melon.dubyadu.de/api/{API_VERSION}/game/"; - - internal static InfoStruct Contact(string response_str) - { - ResponseStruct responseobj = MelonUtils.ParseJSONStringtoStruct(response_str); - if (responseobj == null) - return null; - - InfoStruct returninfo = new InfoStruct(); - returninfo.ForceDumperVersion = responseobj.forceCpp2IlVersion; - returninfo.ObfuscationRegex = responseobj.obfuscationRegex; - returninfo.MappingURL = responseobj.mappingUrl; - returninfo.MappingFileSHA512 = responseobj.mappingFileSHA512; - return returninfo; - } + var responseobj = MelonUtils.ParseJSONStringtoStruct(response_str); + if (responseobj == null) + return null; - internal class ResponseStruct + var returninfo = new InfoStruct { - public string gameSlug = null; - public string gameName = null; - public string mappingUrl = null; - public string mappingFileSHA512 = null; - public string forceCpp2IlVersion = null; - public string forceUnhollowerVersion = null; //TODO: Remove this from the API - public string obfuscationRegex = null; - } + ForceDumperVersion = responseobj.forceCpp2IlVersion, + ObfuscationRegex = responseobj.obfuscationRegex, + MappingURL = responseobj.mappingUrl, + MappingFileSHA512 = responseobj.mappingFileSHA512 + }; + return returninfo; + } + + internal class ResponseStruct + { + public string gameSlug = null; + public string gameName = null; + public string mappingUrl = null; + public string mappingFileSHA512 = null; + public string forceCpp2IlVersion = null; + public string forceUnhollowerVersion = null; //TODO: Remove this from the API + public string obfuscationRegex = null; } } } diff --git a/Dependencies/SupportModules/Component.cs b/Dependencies/SupportModules/Component.cs index 90f034561..1481ccd42 100644 --- a/Dependencies/SupportModules/Component.cs +++ b/Dependencies/SupportModules/Component.cs @@ -6,189 +6,191 @@ using Il2CppInterop.Runtime; #endif -namespace MelonLoader.Support +namespace MelonLoader.Support; + +internal class SM_Component : MonoBehaviour { - internal class SM_Component : MonoBehaviour - { - private bool isQuitting; - private static bool hadError; - private static bool useGeneratedAssembly = true; + private bool isQuitting; + private static bool hadError; + private static bool useGeneratedAssembly = true; - private static MethodInfo SetAsLastSiblingMethod; + private static readonly MethodInfo SetAsLastSiblingMethod; #if SM_Il2Cpp - private delegate bool SetAsLastSiblingDelegate(IntPtr transformptr); - private static SetAsLastSiblingDelegate SetAsLastSiblingDelegateField; - public SM_Component(IntPtr value) : base(value) { } + private delegate bool SetAsLastSiblingDelegate(IntPtr transformptr); + private static readonly SetAsLastSiblingDelegate SetAsLastSiblingDelegateField; + public SM_Component(IntPtr value) : base(value) { } #endif - static SM_Component() + static SM_Component() + { + try { - try - { #if SM_Il2Cpp - SetAsLastSiblingMethod = typeof(Transform).GetMethod("SetAsLastSibling", BindingFlags.Public | BindingFlags.Instance); - if (SetAsLastSiblingMethod != null) - return; + SetAsLastSiblingMethod = typeof(Transform).GetMethod("SetAsLastSibling", BindingFlags.Public | BindingFlags.Instance); + if (SetAsLastSiblingMethod != null) + return; - useGeneratedAssembly = false; - SetAsLastSiblingDelegateField = IL2CPP.ResolveICall("UnityEngine.Transform::SetAsLastSibling"); - if (SetAsLastSiblingDelegateField == null) - throw new Exception("Unable to find Internal Call for UnityEngine.Transform::SetAsLastSibling"); + useGeneratedAssembly = false; + SetAsLastSiblingDelegateField = IL2CPP.ResolveICall("UnityEngine.Transform::SetAsLastSibling"); + if (SetAsLastSiblingDelegateField == null) + throw new Exception("Unable to find Internal Call for UnityEngine.Transform::SetAsLastSibling"); #else - SetAsLastSiblingMethod = typeof(Transform).GetMethod("SetAsLastSibling", BindingFlags.Public | BindingFlags.Instance); - if (SetAsLastSiblingMethod == null) - throw new Exception("Unable to find UnityEngine.Transform::SetAsLastSibling"); + SetAsLastSiblingMethod = typeof(Transform).GetMethod("SetAsLastSibling", BindingFlags.Public | BindingFlags.Instance); + if (SetAsLastSiblingMethod == null) + throw new Exception("Unable to find UnityEngine.Transform::SetAsLastSibling"); #endif - } - catch (Exception ex) { LogError("Getting UnityEngine.Transform::SetAsLastSibling", ex); } } - - internal static void Create() + catch (Exception ex) { - if (Main.component != null) - return; + LogError("Getting UnityEngine.Transform::SetAsLastSibling", ex); + } + } + + internal static void Create() + { + if (Main.component != null) + return; - Main.obj = new GameObject(); - DontDestroyOnLoad(Main.obj); - Main.obj.hideFlags = HideFlags.DontSave; + Main.obj = new GameObject(); + DontDestroyOnLoad(Main.obj); + Main.obj.hideFlags = HideFlags.DontSave; #if SM_Il2Cpp - Main.component = Main.obj.AddComponent(Il2CppType.Of()).TryCast(); + Main.component = Main.obj.AddComponent(Il2CppType.Of()).TryCast(); #else - Main.component = (SM_Component)Main.obj.AddComponent(typeof(SM_Component)); + Main.component = (SM_Component)Main.obj.AddComponent(typeof(SM_Component)); #endif - Main.component.SiblingFix(); - } + Main.component.SiblingFix(); + } - private static void LogError(string cat, Exception ex) - { - hadError = true; - useGeneratedAssembly = false; - MelonLogger.Warning($"Exception while {cat}: {ex}"); - MelonLogger.Warning("Melon Events might run before some MonoBehaviour Events"); - } + private static void LogError(string cat, Exception ex) + { + hadError = true; + useGeneratedAssembly = false; + MelonLogger.Warning($"Exception while {cat}: {ex}"); + MelonLogger.Warning("Melon Events might run before some MonoBehaviour Events"); + } - private void SiblingFix() - { - if (hadError) - return; + private void SiblingFix() + { + if (hadError) + return; - try + try + { + if (useGeneratedAssembly) { - if (useGeneratedAssembly) - { - gameObject.transform.SetAsLastSibling(); - transform.SetAsLastSibling(); - return; - } + gameObject.transform.SetAsLastSibling(); + transform.SetAsLastSibling(); + return; + } #if SM_Il2Cpp - SetAsLastSiblingDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(gameObject.transform)); - SetAsLastSiblingDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(transform)); + SetAsLastSiblingDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(gameObject.transform)); + SetAsLastSiblingDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(transform)); #endif - } - catch (Exception ex) - { + } + catch (Exception ex) + { #if SM_Il2Cpp - if (useGeneratedAssembly) - { - useGeneratedAssembly = false; - SiblingFix(); - return; - } + if (useGeneratedAssembly) + { + useGeneratedAssembly = false; + SiblingFix(); + return; + } #endif - LogError("Invoking UnityEngine.Transform::SetAsLastSibling", ex); - } + LogError("Invoking UnityEngine.Transform::SetAsLastSibling", ex); } + } - void Start() - { - if ((Main.component != null) && (Main.component != this)) - return; + private void Start() + { + if ((Main.component != null) && (Main.component != this)) + return; - SiblingFix(); - Main.Interface.OnApplicationLateStart(); - } + SiblingFix(); + Main.Interface.OnApplicationLateStart(); + } - void Awake() - { - if ((Main.component != null) && (Main.component != this)) - return; + private void Awake() + { + if ((Main.component != null) && (Main.component != this)) + return; - foreach (var queuedCoroutine in SupportModule_To.QueuedCoroutines) + foreach (var queuedCoroutine in SupportModule_To.QueuedCoroutines) #if SM_Il2Cpp - StartCoroutine(new Il2CppSystem.Collections.IEnumerator(new MonoEnumeratorWrapper(queuedCoroutine).Pointer)); + StartCoroutine(new Il2CppSystem.Collections.IEnumerator(new MonoEnumeratorWrapper(queuedCoroutine).Pointer)); #else - StartCoroutine(queuedCoroutine); + StartCoroutine(queuedCoroutine); #endif - SupportModule_To.QueuedCoroutines.Clear(); - } - - void Update() - { - if ((Main.component != null) && (Main.component != this)) - return; + SupportModule_To.QueuedCoroutines.Clear(); + } - isQuitting = false; - SiblingFix(); + private void Update() + { + if ((Main.component != null) && (Main.component != this)) + return; - SceneHandler.OnUpdate(); - Main.Interface.Update(); - } + isQuitting = false; + SiblingFix(); - void OnDestroy() - { - if ((Main.component != null) && (Main.component != this)) - return; + SceneHandler.OnUpdate(); + Main.Interface.Update(); + } - if (!isQuitting) - { - Create(); - return; - } + private void OnDestroy() + { + if ((Main.component != null) && (Main.component != this)) + return; - OnApplicationDefiniteQuit(); + if (!isQuitting) + { + Create(); + return; } - void OnApplicationQuit() - { - if ((Main.component != null) && (Main.component != this)) - return; + OnApplicationDefiniteQuit(); + } - isQuitting = true; - Main.Interface.Quit(); - } + private void OnApplicationQuit() + { + if ((Main.component != null) && (Main.component != this)) + return; - void OnApplicationDefiniteQuit() - { - Main.Interface.DefiniteQuit(); - } + isQuitting = true; + Main.Interface.Quit(); + } - void FixedUpdate() - { - if ((Main.component != null) && (Main.component != this)) - return; + private void OnApplicationDefiniteQuit() + { + Main.Interface.DefiniteQuit(); + } - Main.Interface.FixedUpdate(); - } + private void FixedUpdate() + { + if ((Main.component != null) && (Main.component != this)) + return; - void LateUpdate() - { - if ((Main.component != null) && (Main.component != this)) - return; + Main.Interface.FixedUpdate(); + } - Main.Interface.LateUpdate(); - } + private void LateUpdate() + { + if ((Main.component != null) && (Main.component != this)) + return; - void OnGUI() - { - if ((Main.component != null) && (Main.component != this)) - return; + Main.Interface.LateUpdate(); + } - Main.Interface.OnGUI(); - } + private void OnGUI() + { + if ((Main.component != null) && (Main.component != this)) + return; + + Main.Interface.OnGUI(); } } \ No newline at end of file diff --git a/Dependencies/SupportModules/Il2Cpp/InteropInterface.cs b/Dependencies/SupportModules/Il2Cpp/InteropInterface.cs index 06668b909..9cf4e25e2 100644 --- a/Dependencies/SupportModules/Il2Cpp/InteropInterface.cs +++ b/Dependencies/SupportModules/Il2Cpp/InteropInterface.cs @@ -6,44 +6,42 @@ using System; using System.Reflection; -namespace MelonLoader.Support +namespace MelonLoader.Support; + +internal class InteropInterface : InteropSupport.Interface { - internal class InteropInterface : InteropSupport.Interface + public IntPtr CopyMethodInfoStruct(IntPtr ptr) + => ptr; + //=> UnityVersionHandler.CopyMethodInfoStruct(ptr); + + public int? GetIl2CppMethodCallerCount(MethodBase method) + { + var att = method.GetCustomAttribute(); + return att?.Count; + } + + public FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method) + => Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(method); + + public void RegisterTypeInIl2CppDomain(Type type, bool logSuccess) + => ClassInjector.RegisterTypeInIl2Cpp(type, new() { LogSuccess = logSuccess }); + public void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces, bool logSuccess) + => ClassInjector.RegisterTypeInIl2Cpp(type, new() { LogSuccess = logSuccess, Interfaces = interfaces }); + + public bool IsInheritedFromIl2CppObjectBase(Type type) + => (type != null) && type.IsSubclassOf(typeof(Il2CppObjectBase)); + + public bool IsInjectedType(Type type) + { + var ptr = GetClassPointerForType(type); + return ptr != IntPtr.Zero && RuntimeSpecificsStore.IsInjected(ptr); + } + + public IntPtr GetClassPointerForType(Type type) { - public IntPtr CopyMethodInfoStruct(IntPtr ptr) - => ptr; - //=> UnityVersionHandler.CopyMethodInfoStruct(ptr); - - public int? GetIl2CppMethodCallerCount(MethodBase method) - { - CallerCountAttribute att = method.GetCustomAttribute(); - if (att == null) - return null; - return att.Count; - } - - public FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method) - => Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(method); - - public void RegisterTypeInIl2CppDomain(Type type, bool logSuccess) - => ClassInjector.RegisterTypeInIl2Cpp(type, new() { LogSuccess = logSuccess }); - public void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces, bool logSuccess) - => ClassInjector.RegisterTypeInIl2Cpp(type, new() { LogSuccess = logSuccess, Interfaces = interfaces }); - - public bool IsInheritedFromIl2CppObjectBase(Type type) - => (type != null) && type.IsSubclassOf(typeof(Il2CppObjectBase)); - - public bool IsInjectedType(Type type) - { - IntPtr ptr = GetClassPointerForType(type); - return ptr != IntPtr.Zero && RuntimeSpecificsStore.IsInjected(ptr); - } - - public IntPtr GetClassPointerForType(Type type) - { - if (type == typeof(void)) return Il2CppClassPointerStore.NativeClassPtr; - return (IntPtr)typeof(Il2CppClassPointerStore<>).MakeGenericType(type) - .GetField(nameof(Il2CppClassPointerStore.NativeClassPtr)).GetValue(null); - } + return type == typeof(void) + ? Il2CppClassPointerStore.NativeClassPtr + : (IntPtr)typeof(Il2CppClassPointerStore<>).MakeGenericType(type) + .GetField(nameof(Il2CppClassPointerStore.NativeClassPtr)).GetValue(null); } } diff --git a/Dependencies/SupportModules/Il2Cpp/Main.cs b/Dependencies/SupportModules/Il2Cpp/Main.cs index 21c3d41cc..4f3a01ccd 100644 --- a/Dependencies/SupportModules/Il2Cpp/Main.cs +++ b/Dependencies/SupportModules/Il2Cpp/Main.cs @@ -1,237 +1,237 @@ -using Il2CppInterop.HarmonySupport; +using Il2CppInterop.Common; +using Il2CppInterop.HarmonySupport; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.Startup; +using MelonLoader.CoreClrUtils; using MelonLoader.Support.Preferences; +using MelonLoader.Utils; +using Microsoft.Extensions.Logging; using System; +using System.IO; using System.Reflection; using System.Runtime.InteropServices; -using MelonLoader.CoreClrUtils; using UnityEngine; -using Il2CppInterop.Common; -using Microsoft.Extensions.Logging; -using MelonLoader.Utils; -using System.IO; [assembly: MelonLoader.PatchShield] #pragma warning disable CS0618 // Type or member is obsolete -namespace MelonLoader.Support +namespace MelonLoader.Support; + +internal static class Main { - internal static class Main - { - internal static ISupportModule_From Interface; - internal static InteropInterface Interop; - internal static GameObject obj = null; - internal static SM_Component component = null; + internal static ISupportModule_From Interface; + internal static InteropInterface Interop; + internal static GameObject obj = null; + internal static SM_Component component = null; - private static Assembly Il2Cppmscorlib = null; - private static Type streamType = null; + private static Assembly Il2Cppmscorlib = null; + private static Type streamType = null; - private static ISupportModule_To Initialize(ISupportModule_From interface_from) - { - Interface = interface_from; + private static ISupportModule_To Initialize(ISupportModule_From interface_from) + { + Interface = interface_from; - foreach (var file in Directory.GetFiles(MelonEnvironment.Il2CppAssembliesDirectory, "*.dll")) + foreach (var file in Directory.GetFiles(MelonEnvironment.Il2CppAssembliesDirectory, "*.dll")) + { + try { - try - { - Assembly.LoadFrom(file); - } - catch { } + Assembly.LoadFrom(file); } + catch { } + } - UnityMappers.RegisterMappers(); + UnityMappers.RegisterMappers(); - Il2CppInteropRuntime runtime = Il2CppInteropRuntime.Create(new() - { - DetourProvider = new MelonDetourProvider(), - UnityVersion = new Version( - InternalUtils.UnityInformationHandler.EngineVersion.Major, - InternalUtils.UnityInformationHandler.EngineVersion.Minor, - InternalUtils.UnityInformationHandler.EngineVersion.Build) - }).AddLogger(new InteropLogger()) - .AddHarmonySupport(); + var runtime = Il2CppInteropRuntime.Create(new() + { + DetourProvider = new MelonDetourProvider(), + UnityVersion = new Version( + InternalUtils.UnityInformationHandler.EngineVersion.Major, + InternalUtils.UnityInformationHandler.EngineVersion.Minor, + InternalUtils.UnityInformationHandler.EngineVersion.Build) + }).AddLogger(new InteropLogger()) + .AddHarmonySupport(); - if (MelonLaunchOptions.Console.CleanUnityLogs) - ConsoleCleaner(); + if (MelonLaunchOptions.Console.CleanUnityLogs) + ConsoleCleaner(); - SceneHandler.Init(); + SceneHandler.Init(); - MonoEnumeratorWrapper.Register(); + MonoEnumeratorWrapper.Register(); - ClassInjector.RegisterTypeInIl2Cpp(); - SM_Component.Create(); + ClassInjector.RegisterTypeInIl2Cpp(); + SM_Component.Create(); - Interop = new InteropInterface(); - Interface.SetInteropSupportInterface(Interop); - runtime.Start(); + Interop = new InteropInterface(); + Interface.SetInteropSupportInterface(Interop); + runtime.Start(); - return new SupportModule_To(); - } + return new SupportModule_To(); + } - private static void ConsoleCleaner() + private static void ConsoleCleaner() + { + // Il2CppSystem.Console.SetOut(new Il2CppSystem.IO.StreamWriter(Il2CppSystem.IO.Stream.Null)); + try { - // Il2CppSystem.Console.SetOut(new Il2CppSystem.IO.StreamWriter(Il2CppSystem.IO.Stream.Null)); - try - { - Il2Cppmscorlib = Assembly.Load("Il2Cppmscorlib"); - if (Il2Cppmscorlib == null) - throw new Exception("Unable to Find Assembly Il2Cppmscorlib!"); + Il2Cppmscorlib = Assembly.Load("Il2Cppmscorlib"); + if (Il2Cppmscorlib == null) + throw new Exception("Unable to Find Assembly Il2Cppmscorlib!"); - streamType = Il2Cppmscorlib.GetType("Il2CppSystem.IO.Stream"); - if (streamType == null) - throw new Exception("Unable to Find Type Il2CppSystem.IO.Stream!"); + streamType = Il2Cppmscorlib.GetType("Il2CppSystem.IO.Stream"); + if (streamType == null) + throw new Exception("Unable to Find Type Il2CppSystem.IO.Stream!"); - PropertyInfo propertyInfo = streamType.GetProperty("Null", BindingFlags.Static | BindingFlags.Public); - if (propertyInfo == null) - throw new Exception("Unable to Find Property Il2CppSystem.IO.Stream.Null!"); + var propertyInfo = streamType.GetProperty("Null", BindingFlags.Static | BindingFlags.Public); + if (propertyInfo == null) + throw new Exception("Unable to Find Property Il2CppSystem.IO.Stream.Null!"); - MethodInfo nullStreamField = propertyInfo.GetGetMethod(); - if (nullStreamField == null) - throw new Exception("Unable to Find Get Method of Property Il2CppSystem.IO.Stream.Null!"); + var nullStreamField = propertyInfo.GetGetMethod(); + if (nullStreamField == null) + throw new Exception("Unable to Find Get Method of Property Il2CppSystem.IO.Stream.Null!"); - object nullStream = nullStreamField.Invoke(null, new object[0]); - if (nullStream == null) - throw new Exception("Unable to Get Value of Property Il2CppSystem.IO.Stream.Null!"); + var nullStream = nullStreamField.Invoke(null, new object[0]); + if (nullStream == null) + throw new Exception("Unable to Get Value of Property Il2CppSystem.IO.Stream.Null!"); - Type streamWriterType = Il2Cppmscorlib.GetType("Il2CppSystem.IO.StreamWriter"); - if (streamWriterType == null) - throw new Exception("Unable to Find Type Il2CppSystem.IO.StreamWriter!"); + var streamWriterType = Il2Cppmscorlib.GetType("Il2CppSystem.IO.StreamWriter"); + if (streamWriterType == null) + throw new Exception("Unable to Find Type Il2CppSystem.IO.StreamWriter!"); - ConstructorInfo streamWriterCtor = streamWriterType.GetConstructor(new[] { streamType }); - if (streamWriterCtor == null) - throw new Exception("Unable to Find Constructor of Type Il2CppSystem.IO.StreamWriter!"); + var streamWriterCtor = streamWriterType.GetConstructor(new[] { streamType }); + if (streamWriterCtor == null) + throw new Exception("Unable to Find Constructor of Type Il2CppSystem.IO.StreamWriter!"); - object nullStreamWriter = streamWriterCtor.Invoke(new[] { nullStream }); - if (nullStreamWriter == null) - throw new Exception("Unable to Invoke Constructor of Type Il2CppSystem.IO.StreamWriter!"); + var nullStreamWriter = streamWriterCtor.Invoke(new[] { nullStream }); + if (nullStreamWriter == null) + throw new Exception("Unable to Invoke Constructor of Type Il2CppSystem.IO.StreamWriter!"); - Type consoleType = Il2Cppmscorlib.GetType("Il2CppSystem.Console"); - if (consoleType == null) - throw new Exception("Unable to Find Type Il2CppSystem.Console!"); + var consoleType = Il2Cppmscorlib.GetType("Il2CppSystem.Console"); + if (consoleType == null) + throw new Exception("Unable to Find Type Il2CppSystem.Console!"); - MethodInfo setOutMethod = consoleType.GetMethod("SetOut", BindingFlags.Static | BindingFlags.Public); - if (setOutMethod == null) - throw new Exception("Unable to Find Method Il2CppSystem.Console.SetOut!"); + var setOutMethod = consoleType.GetMethod("SetOut", BindingFlags.Static | BindingFlags.Public); + if (setOutMethod == null) + throw new Exception("Unable to Find Method Il2CppSystem.Console.SetOut!"); - setOutMethod.Invoke(null, new[] { nullStreamWriter }); - } - catch (Exception ex) { MelonLogger.Warning($"Console Cleaner Failed: {ex}"); } + setOutMethod.Invoke(null, new[] { nullStreamWriter }); + } + catch (Exception ex) + { + MelonLogger.Warning($"Console Cleaner Failed: {ex}"); } } +} + +internal sealed class MelonDetourProvider : IDetourProvider +{ + public IDetour Create(nint original, TDelegate target) where TDelegate : Delegate + { + return new MelonDetour(original, target); + } - internal sealed class MelonDetourProvider : IDetourProvider + private sealed class MelonDetour : IDetour { - public IDetour Create(nint original, TDelegate target) where TDelegate : Delegate + private readonly nint _detourFrom; + private nint _originalPtr; + + private readonly Delegate _target; + private IntPtr _targetPtr; + + /// + /// Original method + /// + public nint Target => _detourFrom; + + public nint Detour => _targetPtr; + public nint OriginalTrampoline => _originalPtr; + + public MelonDetour(nint detourFrom, Delegate target) { - return new MelonDetour(original, target); + _detourFrom = detourFrom; + _target = target; + + // We have to apply immediately because we're gonna be asked for a trampoline right away + Apply(); } - private sealed class MelonDetour : IDetour + public unsafe void Apply() { - private nint _detourFrom; - private nint _originalPtr; - - private Delegate _target; - private IntPtr _targetPtr; - - /// - /// Original method - /// - public nint Target => _detourFrom; - - public nint Detour => _targetPtr; - public nint OriginalTrampoline => _originalPtr; - - public MelonDetour(nint detourFrom, Delegate target) - { - _detourFrom = detourFrom; - _target = target; + if (_targetPtr != IntPtr.Zero) + return; - // We have to apply immediately because we're gonna be asked for a trampoline right away - Apply(); - } + _targetPtr = Marshal.GetFunctionPointerForDelegate(_target); - public unsafe void Apply() - { - if (_targetPtr != IntPtr.Zero) - return; - - _targetPtr = Marshal.GetFunctionPointerForDelegate(_target); - - var addr = _detourFrom; - nint addrPtr = (nint)(&addr); - MelonUtils.NativeHookAttachDirect(addrPtr, _targetPtr); - NativeStackWalk.RegisterHookAddr((ulong)addrPtr, $"Il2CppInterop detour of 0x{addrPtr:X} -> 0x{_targetPtr:X}"); + var addr = _detourFrom; + var addrPtr = (nint)(&addr); + MelonUtils.NativeHookAttachDirect(addrPtr, _targetPtr); + NativeStackWalk.RegisterHookAddr((ulong)addrPtr, $"Il2CppInterop detour of 0x{addrPtr:X} -> 0x{_targetPtr:X}"); - _originalPtr = addr; - } + _originalPtr = addr; + } - public unsafe void Dispose() - { - if (_targetPtr == IntPtr.Zero) - return; + public unsafe void Dispose() + { + if (_targetPtr == IntPtr.Zero) + return; - var addr = _detourFrom; - nint addrPtr = (nint)(&addr); + var addr = _detourFrom; + var addrPtr = (nint)(&addr); - MelonUtils.NativeHookDetach(addrPtr, _targetPtr); - NativeStackWalk.UnregisterHookAddr((ulong)addrPtr); + MelonUtils.NativeHookDetach(addrPtr, _targetPtr); + NativeStackWalk.UnregisterHookAddr((ulong)addrPtr); - _targetPtr = IntPtr.Zero; - _originalPtr = IntPtr.Zero; - } + _targetPtr = IntPtr.Zero; + _originalPtr = IntPtr.Zero; + } - public T GenerateTrampoline() - where T : Delegate - { - if (_originalPtr == IntPtr.Zero) - return null; - return Marshal.GetDelegateForFunctionPointer(_originalPtr); - } + public T GenerateTrampoline() + where T : Delegate + { + return _originalPtr == IntPtr.Zero ? null : Marshal.GetDelegateForFunctionPointer(_originalPtr); } } +} - internal class InteropLogger - : Microsoft.Extensions.Logging.ILogger - { - private MelonLogger.Instance _logger = new("Il2CppInterop"); +internal class InteropLogger + : Microsoft.Extensions.Logging.ILogger +{ + private readonly MelonLogger.Instance _logger = new("Il2CppInterop"); - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, - Func formatter) + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, + Func formatter) + { + var formattedTxt = formatter(state, exception); + switch (logLevel) { - string formattedTxt = formatter(state, exception); - switch (logLevel) - { - case LogLevel.Debug: - case LogLevel.Trace: - MelonDebug.Msg(formattedTxt); - break; - - case LogLevel.Error: - _logger.Error(formattedTxt); - break; - - case LogLevel.Warning: - _logger.Warning(formattedTxt); - break; - - case LogLevel.Information: - default: - _logger.Msg(formattedTxt); - break; - } + case LogLevel.Debug: + case LogLevel.Trace: + MelonDebug.Msg(formattedTxt); + break; + + case LogLevel.Error: + _logger.Error(formattedTxt); + break; + + case LogLevel.Warning: + _logger.Warning(formattedTxt); + break; + + case LogLevel.Information: + default: + _logger.Msg(formattedTxt); + break; } + } - public bool IsEnabled(LogLevel logLevel) - => logLevel switch - { - LogLevel.Debug or LogLevel.Trace => MelonDebug.IsEnabled(), - _ => true - }; + public bool IsEnabled(LogLevel logLevel) + => logLevel switch + { + LogLevel.Debug or LogLevel.Trace => MelonDebug.IsEnabled(), + _ => true + }; - public IDisposable BeginScope(TState state) - => throw new NotImplementedException(); - } + public IDisposable BeginScope(TState state) + => throw new NotImplementedException(); } diff --git a/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs b/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs index e55aecf2a..fa30e8f69 100644 --- a/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs +++ b/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs @@ -2,54 +2,54 @@ using System; using System.Collections; -namespace MelonLoader.Support +namespace MelonLoader.Support; + +public class MonoEnumeratorWrapper : Il2CppSystem.Object /*, IEnumerator */ { - public class MonoEnumeratorWrapper : Il2CppSystem.Object /*, IEnumerator */ + internal static unsafe void Register() + => ClassInjector.RegisterTypeInIl2Cpp(new() + { + LogSuccess = true, + Interfaces = new Type[] { typeof(Il2CppSystem.Collections.IEnumerator) } + }); + + private readonly IEnumerator enumerator; + public MonoEnumeratorWrapper(IntPtr ptr) : base(ptr) { } + public MonoEnumeratorWrapper(IEnumerator _enumerator) : base(ClassInjector.DerivedConstructorPointer()) { - internal unsafe static void Register() - => ClassInjector.RegisterTypeInIl2Cpp(new() - { - LogSuccess = true, - Interfaces = new Type[] { typeof(Il2CppSystem.Collections.IEnumerator) } - }); - - private readonly IEnumerator enumerator; - public MonoEnumeratorWrapper(IntPtr ptr) : base(ptr) { } - public MonoEnumeratorWrapper(IEnumerator _enumerator) : base(ClassInjector.DerivedConstructorPointer()) + ClassInjector.DerivedConstructorBody(this); + enumerator = _enumerator ?? throw new NullReferenceException("routine is null"); + } + + public Il2CppSystem.Object /*IEnumerator.*/Current + { + get => enumerator.Current switch { - ClassInjector.DerivedConstructorBody(this); - enumerator = _enumerator ?? throw new NullReferenceException("routine is null"); - } + IEnumerator next => new MonoEnumeratorWrapper(next), + Il2CppSystem.Object il2cppObject => il2cppObject, + null => null, + _ => throw new NotSupportedException($"{enumerator.GetType()}: Unsupported type {enumerator.Current.GetType()}"), + }; + } - public Il2CppSystem.Object /*IEnumerator.*/Current + public bool MoveNext() + { + try { - get => enumerator.Current switch - { - IEnumerator next => new MonoEnumeratorWrapper(next), - Il2CppSystem.Object il2cppObject => il2cppObject, - null => null, - _ => throw new NotSupportedException($"{enumerator.GetType()}: Unsupported type {enumerator.Current.GetType()}"), - }; + return enumerator.MoveNext(); } - - public bool MoveNext() + catch (Exception e) { - try - { - return enumerator.MoveNext(); - } catch(Exception e) - { - var melon = MelonUtils.GetMelonFromStackTrace(new System.Diagnostics.StackTrace(e), true); - - if (melon != null) - melon.LoggerInstance.Error("Unhandled exception in coroutine. It will not continue executing.", e); - else - MelonLogger.Error("[Error: Could not identify source] Unhandled exception in coroutine. It will not continue executing.", e); - - return false; - } + var melon = MelonUtils.GetMelonFromStackTrace(new System.Diagnostics.StackTrace(e), true); + + if (melon != null) + melon.LoggerInstance.Error("Unhandled exception in coroutine. It will not continue executing.", e); + else + MelonLogger.Error("[Error: Could not identify source] Unhandled exception in coroutine. It will not continue executing.", e); + + return false; } - - public void Reset() => enumerator.Reset(); } + + public void Reset() => enumerator.Reset(); } diff --git a/Dependencies/SupportModules/Mono/Main.cs b/Dependencies/SupportModules/Mono/Main.cs index a12bce579..f1d63e7b9 100644 --- a/Dependencies/SupportModules/Mono/Main.cs +++ b/Dependencies/SupportModules/Mono/Main.cs @@ -1,47 +1,46 @@ -using System; +using MelonLoader.Support.Preferences; using System.Reflection; -using MelonLoader.Support.Preferences; using UnityEngine; [assembly: MelonLoader.PatchShield] -namespace MelonLoader.Support +namespace MelonLoader.Support; + +internal static class Main { - internal static class Main + internal static ISupportModule_From Interface = null; + internal static GameObject obj = null; + internal static SM_Component component = null; + + private static ISupportModule_To Initialize(ISupportModule_From interface_from) { - internal static ISupportModule_From Interface = null; - internal static GameObject obj = null; - internal static SM_Component component = null; + Interface = interface_from; + UnityMappers.RegisterMappers(); - private static ISupportModule_To Initialize(ISupportModule_From interface_from) - { - Interface = interface_from; - UnityMappers.RegisterMappers(); + if (IsUnity53OrLower()) + SM_Component.Create(); + else + SceneHandler.Init(); - if (IsUnity53OrLower()) - SM_Component.Create(); - else - SceneHandler.Init(); + return new SupportModule_To(); + } - return new SupportModule_To(); + private static bool IsUnity53OrLower() + { + try + { + var unityengine = Assembly.Load("UnityEngine"); + if (unityengine == null) + return true; + var scenemanager = unityengine.GetType("UnityEngine.SceneManagement.SceneManager"); + if (scenemanager == null) + return true; + var sceneLoaded = scenemanager.GetEvent("sceneLoaded"); + return sceneLoaded == null; } - - private static bool IsUnity53OrLower() + catch { - try - { - Assembly unityengine = Assembly.Load("UnityEngine"); - if (unityengine == null) - return true; - Type scenemanager = unityengine.GetType("UnityEngine.SceneManagement.SceneManager"); - if (scenemanager == null) - return true; - EventInfo sceneLoaded = scenemanager.GetEvent("sceneLoaded"); - if (sceneLoaded == null) - return true; - return false; - } - catch { return true; } + return true; } } } \ No newline at end of file diff --git a/Dependencies/SupportModules/SceneHandler.cs b/Dependencies/SupportModules/SceneHandler.cs index 01c4e45f5..230327d83 100644 --- a/Dependencies/SupportModules/SceneHandler.cs +++ b/Dependencies/SupportModules/SceneHandler.cs @@ -1,95 +1,100 @@ using System; -using UnityEngine.SceneManagement; using System.Collections.Generic; +using UnityEngine.SceneManagement; #if SM_Il2Cpp using UnityEngine.Events; #endif #pragma warning disable CA2013 -namespace MelonLoader.Support +namespace MelonLoader.Support; + +internal static class SceneHandler { - internal static class SceneHandler + internal class SceneInitEvent { - internal class SceneInitEvent - { - internal int buildIndex; - internal string name; - internal bool wasLoadedThisTick; - } + internal int buildIndex; + internal string name; + internal bool wasLoadedThisTick; + } - private static Queue scenesLoaded = new Queue(); + private static readonly Queue scenesLoaded = new(); - internal static void Init() + internal static void Init() + { + try { - try - { #if SM_Il2Cpp - SceneManager.sceneLoaded = ( - (ReferenceEquals(SceneManager.sceneLoaded, null)) - ? new Action(OnSceneLoad) - : Il2CppSystem.Delegate.Combine(SceneManager.sceneLoaded, (UnityAction)new Action(OnSceneLoad)).Cast>() - ); + SceneManager.sceneLoaded = + ReferenceEquals(SceneManager.sceneLoaded, null) + ? new Action(OnSceneLoad) + : Il2CppSystem.Delegate.Combine(SceneManager.sceneLoaded, (UnityAction)new Action(OnSceneLoad)).Cast>() + ; #else - SceneManager.sceneLoaded += OnSceneLoad; + SceneManager.sceneLoaded += OnSceneLoad; #endif - } - catch (Exception ex) { MelonLogger.Error($"SceneManager.sceneLoaded override failed: {ex}"); } + } + catch (Exception ex) + { + MelonLogger.Error($"SceneManager.sceneLoaded override failed: {ex}"); + } - try - { + try + { #if SM_Il2Cpp - SceneManager.sceneUnloaded = ( - (ReferenceEquals(SceneManager.sceneUnloaded, null)) - ? new Action(OnSceneUnload) - : Il2CppSystem.Delegate.Combine(SceneManager.sceneUnloaded, (UnityAction)new Action(OnSceneUnload)).Cast>() - ); + SceneManager.sceneUnloaded = + ReferenceEquals(SceneManager.sceneUnloaded, null) + ? new Action(OnSceneUnload) + : Il2CppSystem.Delegate.Combine(SceneManager.sceneUnloaded, (UnityAction)new Action(OnSceneUnload)).Cast>() + ; #else - SceneManager.sceneUnloaded += OnSceneUnload; + SceneManager.sceneUnloaded += OnSceneUnload; #endif - } - catch (Exception ex) { MelonLogger.Error($"SceneManager.sceneUnloaded override failed: {ex}"); } } - - private static void OnSceneLoad(Scene scene, LoadSceneMode mode) + catch (Exception ex) { - if (Main.obj == null) - SM_Component.Create(); + MelonLogger.Error($"SceneManager.sceneUnloaded override failed: {ex}"); + } + } - if (ReferenceEquals(scene, null)) - return; + private static void OnSceneLoad(Scene scene, LoadSceneMode mode) + { + if (Main.obj == null) + SM_Component.Create(); - Main.Interface.OnSceneWasLoaded(scene.buildIndex, scene.name); - scenesLoaded.Enqueue(new SceneInitEvent { buildIndex = scene.buildIndex, name = scene.name }); - } + if (ReferenceEquals(scene, null)) + return; - private static void OnSceneUnload(Scene scene) - { - if (ReferenceEquals(scene, null)) - return; + Main.Interface.OnSceneWasLoaded(scene.buildIndex, scene.name); + scenesLoaded.Enqueue(new SceneInitEvent { buildIndex = scene.buildIndex, name = scene.name }); + } - Main.Interface.OnSceneWasUnloaded(scene.buildIndex, scene.name); - } + private static void OnSceneUnload(Scene scene) + { + if (ReferenceEquals(scene, null)) + return; - internal static void OnUpdate() + Main.Interface.OnSceneWasUnloaded(scene.buildIndex, scene.name); + } + + internal static void OnUpdate() + { + if (scenesLoaded.Count > 0) { - if (scenesLoaded.Count > 0) + var requeue = new Queue(); + SceneInitEvent evt; + while ((scenesLoaded.Count > 0) && ((evt = scenesLoaded.Dequeue()) != null)) { - Queue requeue = new Queue(); - SceneInitEvent evt = null; - while ((scenesLoaded.Count > 0) && ((evt = scenesLoaded.Dequeue()) != null)) + if (evt.wasLoadedThisTick) + Main.Interface.OnSceneWasInitialized(evt.buildIndex, evt.name); + else { - if (evt.wasLoadedThisTick) - Main.Interface.OnSceneWasInitialized(evt.buildIndex, evt.name); - else - { - evt.wasLoadedThisTick = true; - requeue.Enqueue(evt); - } + evt.wasLoadedThisTick = true; + requeue.Enqueue(evt); } - while ((requeue.Count > 0) && ((evt = requeue.Dequeue()) != null)) - scenesLoaded.Enqueue(evt); } + while ((requeue.Count > 0) && ((evt = requeue.Dequeue()) != null)) + scenesLoaded.Enqueue(evt); } } } diff --git a/Dependencies/SupportModules/SupportModule_To.cs b/Dependencies/SupportModules/SupportModule_To.cs index c33a4b839..d6cef384d 100644 --- a/Dependencies/SupportModules/SupportModule_To.cs +++ b/Dependencies/SupportModules/SupportModule_To.cs @@ -2,29 +2,28 @@ using System.Collections.Generic; using UnityEngine; -namespace MelonLoader.Support +namespace MelonLoader.Support; + +internal class SupportModule_To : ISupportModule_To { - internal class SupportModule_To : ISupportModule_To + internal static readonly List QueuedCoroutines = []; + public object StartCoroutine(IEnumerator coroutine) { - internal static readonly List QueuedCoroutines = new List(); - public object StartCoroutine(IEnumerator coroutine) - { - if (Main.component != null) + if (Main.component != null) #if SM_Il2Cpp - return Main.component.StartCoroutine(new Il2CppSystem.Collections.IEnumerator(new MonoEnumeratorWrapper(coroutine).Pointer)); + return Main.component.StartCoroutine(new Il2CppSystem.Collections.IEnumerator(new MonoEnumeratorWrapper(coroutine).Pointer)); #else - return Main.component.StartCoroutine(coroutine); + return Main.component.StartCoroutine(coroutine); #endif - QueuedCoroutines.Add(coroutine); - return coroutine; - } - public void StopCoroutine(object coroutineToken) - { - if (Main.component == null) - QueuedCoroutines.Remove(coroutineToken as IEnumerator); - else - Main.component.StopCoroutine(coroutineToken as Coroutine); - } - public void UnityDebugLog(string msg) => Debug.Log(msg); + QueuedCoroutines.Add(coroutine); + return coroutine; + } + public void StopCoroutine(object coroutineToken) + { + if (Main.component == null) + QueuedCoroutines.Remove(coroutineToken as IEnumerator); + else + Main.component.StopCoroutine(coroutineToken as Coroutine); } + public void UnityDebugLog(string msg) => Debug.Log(msg); } \ No newline at end of file diff --git a/Dependencies/SupportModules/UnityMappers.cs b/Dependencies/SupportModules/UnityMappers.cs index 9646a9866..5b48e0bfe 100644 --- a/Dependencies/SupportModules/UnityMappers.cs +++ b/Dependencies/SupportModules/UnityMappers.cs @@ -2,102 +2,91 @@ using Tomlet.Models; using UnityEngine; -namespace MelonLoader.Support.Preferences +namespace MelonLoader.Support.Preferences; + +internal static class UnityMappers { - internal static class UnityMappers + internal static void RegisterMappers() { - internal static void RegisterMappers() - { - TomletMain.RegisterMapper(WriteColor, ReadColor); - TomletMain.RegisterMapper(WriteColor32, ReadColor32); - TomletMain.RegisterMapper(WriteVector2, ReadVector2); - TomletMain.RegisterMapper(WriteVector3, ReadVector3); - TomletMain.RegisterMapper(WriteVector4, ReadVector4); - TomletMain.RegisterMapper(WriteQuaternion, ReadQuaternion); - } + TomletMain.RegisterMapper(WriteColor, ReadColor); + TomletMain.RegisterMapper(WriteColor32, ReadColor32); + TomletMain.RegisterMapper(WriteVector2, ReadVector2); + TomletMain.RegisterMapper(WriteVector3, ReadVector3); + TomletMain.RegisterMapper(WriteVector4, ReadVector4); + TomletMain.RegisterMapper(WriteQuaternion, ReadQuaternion); + } - private static Color ReadColor(TomlValue value) - { - float[] floats = MelonPreferences.Mapper.ReadArray(value); - if (floats == null || floats.Length != 4) - return default; - return new Color(floats[0] / 255f, floats[1] / 255f, floats[2] / 255f, floats[3] / 255f); - } + private static Color ReadColor(TomlValue value) + { + var floats = MelonPreferences.Mapper.ReadArray(value); + return floats == null || floats.Length != 4 + ? default + : new Color(floats[0] / 255f, floats[1] / 255f, floats[2] / 255f, floats[3] / 255f); + } - private static TomlValue WriteColor(Color value) - { - float[] floats = new[] { value.r * 255, value.g * 255, value.b * 255, value.a * 255}; - return MelonPreferences.Mapper.WriteArray(floats); - } + private static TomlValue WriteColor(Color value) + { + var floats = new[] { value.r * 255, value.g * 255, value.b * 255, value.a * 255 }; + return MelonPreferences.Mapper.WriteArray(floats); + } - private static Color32 ReadColor32(TomlValue value) - { - byte[] bytes = MelonPreferences.Mapper.ReadArray(value); - if (bytes == null || bytes.Length != 4) - return default; - return new Color32(bytes[0], bytes[1], bytes[2], bytes[3]); - } + private static Color32 ReadColor32(TomlValue value) + { + var bytes = MelonPreferences.Mapper.ReadArray(value); + return bytes == null || bytes.Length != 4 ? default : new Color32(bytes[0], bytes[1], bytes[2], bytes[3]); + } - private static TomlValue WriteColor32(Color32 value) - { - byte[] bytes = new[] { value.r, value.g, value.b, value.a }; - return MelonPreferences.Mapper.WriteArray(bytes); - } + private static TomlValue WriteColor32(Color32 value) + { + var bytes = new[] { value.r, value.g, value.b, value.a }; + return MelonPreferences.Mapper.WriteArray(bytes); + } - private static Vector2 ReadVector2(TomlValue value) - { - float[] floats = MelonPreferences.Mapper.ReadArray(value); - if (floats == null || floats.Length != 2) - return default; - return new Vector2(floats[0], floats[1]); - } + private static Vector2 ReadVector2(TomlValue value) + { + var floats = MelonPreferences.Mapper.ReadArray(value); + return floats == null || floats.Length != 2 ? default : new Vector2(floats[0], floats[1]); + } - private static TomlValue WriteVector2(Vector2 value) - { - float[] floats = new[] { value.x, value.y }; - return MelonPreferences.Mapper.WriteArray(floats); - } + private static TomlValue WriteVector2(Vector2 value) + { + var floats = new[] { value.x, value.y }; + return MelonPreferences.Mapper.WriteArray(floats); + } - private static Vector3 ReadVector3(TomlValue value) - { - float[] floats = MelonPreferences.Mapper.ReadArray(value); - if (floats == null || floats.Length != 3) - return default; - return new Vector3(floats[0], floats[1], floats[2]); - } + private static Vector3 ReadVector3(TomlValue value) + { + var floats = MelonPreferences.Mapper.ReadArray(value); + return floats == null || floats.Length != 3 ? default : new Vector3(floats[0], floats[1], floats[2]); + } - private static TomlValue WriteVector3(Vector3 value) - { - float[] floats = new[] { value.x, value.y, value.z }; - return MelonPreferences.Mapper.WriteArray(floats); - } + private static TomlValue WriteVector3(Vector3 value) + { + var floats = new[] { value.x, value.y, value.z }; + return MelonPreferences.Mapper.WriteArray(floats); + } - private static Vector4 ReadVector4(TomlValue value) - { - float[] floats = MelonPreferences.Mapper.ReadArray(value); - if (floats == null || floats.Length != 4) - return default; - return new Vector4(floats[0], floats[1], floats[2], floats[3]); - } + private static Vector4 ReadVector4(TomlValue value) + { + var floats = MelonPreferences.Mapper.ReadArray(value); + return floats == null || floats.Length != 4 ? default : new Vector4(floats[0], floats[1], floats[2], floats[3]); + } - private static TomlValue WriteVector4(Vector4 value) - { - float[] floats = new[] { value.x, value.y, value.z, value.w }; - return MelonPreferences.Mapper.WriteArray(floats); - } + private static TomlValue WriteVector4(Vector4 value) + { + var floats = new[] { value.x, value.y, value.z, value.w }; + return MelonPreferences.Mapper.WriteArray(floats); + } - private static Quaternion ReadQuaternion(TomlValue value) - { - float[] floats = MelonPreferences.Mapper.ReadArray(value); - if (floats == null || floats.Length != 4) - return default; - return new Quaternion(floats[0], floats[1], floats[2], floats[3]); - } + private static Quaternion ReadQuaternion(TomlValue value) + { + var floats = MelonPreferences.Mapper.ReadArray(value); + return floats == null || floats.Length != 4 ? default : new Quaternion(floats[0], floats[1], floats[2], floats[3]); + } - private static TomlValue WriteQuaternion(Quaternion value) - { - float[] floats = new[] { value.x, value.y, value.z, value.w }; - return MelonPreferences.Mapper.WriteArray(floats); - } + private static TomlValue WriteQuaternion(Quaternion value) + { + var floats = new[] { value.x, value.y, value.z, value.w }; + return MelonPreferences.Mapper.WriteArray(floats); } } \ No newline at end of file From bb668995917608d6d7e77e6bf218a2fd929f26af Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 20:16:36 +0100 Subject: [PATCH 08/18] Cleanup MelonLoader --- MelonLoader/Assertions/LemonAssert.cs | 217 +- .../Assertions/LemonAssertException.cs | 29 +- MelonLoader/Assertions/LemonAssertMapping.cs | 71 +- .../Harmony/Attributes.cs | 311 +- .../Harmony/Extras/DelegateTypeFactory.cs | 7 +- .../Harmony/Extras/FastAccess.cs | 169 +- .../Harmony/Extras/MethodInvoker.cs | 21 +- .../Harmony/HarmonyInstance.cs | 36 +- .../Harmony/HarmonyMethod.cs | 59 +- .../BackwardsCompatibility/Harmony/Patch.cs | 73 +- .../Harmony/Priority.cs | 45 +- .../Harmony/Tools/AccessTools.cs | 93 +- .../Harmony/Tools/Extensions.cs | 61 +- .../Harmony/Tools/SymbolExtensions.cs | 25 +- .../Melon/AssemblyResolveInfo.cs | 9 +- .../Melon/HarmonyShield.cs | 11 +- .../BackwardsCompatibility/Melon/Imports.cs | 49 +- .../BackwardsCompatibility/Melon/Main.cs | 33 +- .../Melon/MelonConsole.cs | 13 +- .../Melon/MelonLoaderBase.cs | 21 +- .../Melon/MelonModGameAttribute.cs | 21 +- .../Melon/MelonModInfoAttribute.cs | 33 +- .../Melon/MelonModLogger.cs | 9 +- .../Melon/MelonPluginGameAttribute.cs | 21 +- .../Melon/MelonPluginInfoAttribute.cs | 33 +- .../Melon/MelonPrefs.cs | 292 +- .../BackwardsCompatibility/Melon/ModPrefs.cs | 87 +- .../Melon/MonoLibrary.cs | 9 +- .../Melon/MonoResolveManager.cs | 57 +- .../BackwardsCompatibility/Melon/bHaptics.cs | 430 +- MelonLoader/Core.cs | 301 +- MelonLoader/EnumExtensions.cs | 39 +- MelonLoader/Fixes/DetourContextDisposeFix.cs | 79 +- MelonLoader/Fixes/ForcedCultureInfo.cs | 102 +- MelonLoader/Fixes/InstancePatchFix.cs | 54 +- MelonLoader/Fixes/Net20Compatibility.cs | 35 +- MelonLoader/Fixes/ProcessFix.cs | 187 +- .../Fixes/ServerCertificateValidation.cs | 86 +- MelonLoader/Fixes/UnhandledException.cs | 15 +- MelonLoader/HarmonyDontPatchAllAttribute.cs | 9 +- .../ICSharpCode/SharpZipLib/BZip2/BZip2.cs | 133 +- .../SharpZipLib/BZip2/BZip2Constants.cs | 209 +- .../SharpZipLib/BZip2/BZip2Exception.cs | 91 +- .../SharpZipLib/BZip2/BZip2InputStream.cs | 2043 ++-- .../SharpZipLib/BZip2/BZip2OutputStream.cs | 4035 ++++--- .../SharpZipLib/Checksum/Adler32.cs | 293 +- .../SharpZipLib/Checksum/BZip2Crc.cs | 331 +- .../ICSharpCode/SharpZipLib/Checksum/Crc32.cs | 335 +- .../SharpZipLib/Checksum/CrcUtilities.cs | 278 +- .../SharpZipLib/Checksum/IChecksum.cs | 85 +- .../ICSharpCode/SharpZipLib/Core/EmptyRefs.cs | 17 +- .../Core/Exceptions/SharpZipBaseException.cs | 99 +- .../Exceptions/StreamDecodingException.cs | 81 +- .../Exceptions/StreamUnsupportedException.cs | 79 +- .../UnexpectedEndOfStreamException.cs | 79 +- .../Exceptions/ValueOutOfRangeException.cs | 111 +- .../SharpZipLib/Core/FileSystemScanner.cs | 1073 +- .../SharpZipLib/Core/INameTransform.cs | 37 +- .../SharpZipLib/Core/IScanFilter.cs | 25 +- .../SharpZipLib/Core/InvalidNameException.cs | 89 +- .../SharpZipLib/Core/NameFilter.cs | 491 +- .../SharpZipLib/Core/PathFilter.cs | 625 +- .../ICSharpCode/SharpZipLib/Core/PathUtils.cs | 88 +- .../SharpZipLib/Core/StreamUtils.cs | 557 +- .../SharpZipLib/Encryption/PkzipClassic.cs | 957 +- .../SharpZipLib/Encryption/ZipAESStream.cs | 449 +- .../SharpZipLib/Encryption/ZipAESTransform.cs | 428 +- .../SharpZipLib/GZip/GZIPConstants.cs | 128 +- .../ICSharpCode/SharpZipLib/GZip/GZip.cs | 154 +- .../SharpZipLib/GZip/GZipException.cs | 91 +- .../SharpZipLib/GZip/GzipInputStream.cs | 704 +- .../SharpZipLib/GZip/GzipOutputStream.cs | 528 +- .../SharpZipLib/Lzw/LzwConstants.cs | 121 +- .../SharpZipLib/Lzw/LzwException.cs | 91 +- .../SharpZipLib/Lzw/LzwInputStream.cs | 1131 +- .../SharpZipLib/Tar/InvalidHeaderException.cs | 93 +- .../ICSharpCode/SharpZipLib/Tar/TarArchive.cs | 1958 ++-- .../ICSharpCode/SharpZipLib/Tar/TarBuffer.cs | 1185 +- .../ICSharpCode/SharpZipLib/Tar/TarEntry.cs | 1169 +- .../SharpZipLib/Tar/TarException.cs | 91 +- .../Tar/TarExtendedHeaderReader.cs | 187 +- .../ICSharpCode/SharpZipLib/Tar/TarHeader.cs | 2570 +++-- .../SharpZipLib/Tar/TarInputStream.cs | 1529 ++- .../SharpZipLib/Tar/TarOutputStream.cs | 1023 +- .../SharpZipLib/Zip/Compression/Deflater.cs | 1199 +- .../Zip/Compression/DeflaterConstants.cs | 283 +- .../Zip/Compression/DeflaterEngine.cs | 1734 +-- .../Zip/Compression/DeflaterHuffman.cs | 1847 ++-- .../Zip/Compression/DeflaterPending.cs | 29 +- .../SharpZipLib/Zip/Compression/Inflater.cs | 1752 ++- .../Zip/Compression/InflaterDynHeader.cs | 302 +- .../Zip/Compression/InflaterHuffmanTree.cs | 462 +- .../Zip/Compression/PendingBuffer.cs | 395 +- .../Streams/DeflaterOutputStream.cs | 861 +- .../Streams/InflaterInputStream.cs | 1393 ++- .../Zip/Compression/Streams/OutputWindow.cs | 427 +- .../Compression/Streams/StreamManipulator.cs | 533 +- .../ICSharpCode/SharpZipLib/Zip/FastZip.cs | 1892 ++-- .../SharpZipLib/Zip/IEntryFactory.cs | 109 +- .../SharpZipLib/Zip/WindowsNameTransform.cs | 500 +- .../SharpZipLib/Zip/ZipConstants.cs | 1027 +- .../SharpZipLib/Zip/ZipEncryptionMethod.cs | 45 +- .../ICSharpCode/SharpZipLib/Zip/ZipEntry.cs | 2264 ++-- .../SharpZipLib/Zip/ZipEntryExtensions.cs | 51 +- .../SharpZipLib/Zip/ZipEntryFactory.cs | 688 +- .../SharpZipLib/Zip/ZipException.cs | 91 +- .../SharpZipLib/Zip/ZipExtraData.cs | 1922 ++-- .../ICSharpCode/SharpZipLib/Zip/ZipFile.cs | 9715 ++++++++--------- .../SharpZipLib/Zip/ZipHelperStream.cs | 1242 ++- .../SharpZipLib/Zip/ZipInputStream.cs | 1414 ++- .../SharpZipLib/Zip/ZipNameTransform.cs | 593 +- .../SharpZipLib/Zip/ZipOutputStream.cs | 2115 ++-- .../ICSharpCode/SharpZipLib/Zip/ZipStrings.cs | 372 +- MelonLoader/ISupportModule_From.cs | 29 +- MelonLoader/ISupportModule_To.cs | 13 +- MelonLoader/IniFile.cs | 117 +- MelonLoader/InternalUtils/BootstrapInterop.cs | 1 - MelonLoader/InternalUtils/DependencyGraph.cs | 443 +- MelonLoader/InternalUtils/MelonStartScreen.cs | 35 +- .../InternalUtils/UnityInformationHandler.cs | 386 +- .../InternalUtils/UnityMagicMethods.cs | 261 +- MelonLoader/InteropSupport.cs | 229 +- MelonLoader/LemonAction.cs | 23 +- MelonLoader/LemonArraySegment.cs | 407 +- MelonLoader/LemonEnumerator.cs | 110 +- MelonLoader/LemonFunc.cs | 23 +- MelonLoader/LemonTuple.cs | 199 +- MelonLoader/Lemons/Cryptography/LemonMD5.cs | 33 +- .../Lemons/Cryptography/LemonSHA256.cs | 36 +- .../Lemons/Cryptography/LemonSHA512.cs | 33 +- MelonLoader/Melon.cs | 33 +- MelonLoader/MelonAction.cs | 47 +- .../MelonAdditionalCreditsAttribute.cs | 29 +- .../MelonAdditionalDependenciesAttribute.cs | 19 +- MelonLoader/MelonAssembly.cs | 464 +- MelonLoader/MelonAuthorColorAttribute.cs | 51 +- MelonLoader/MelonBase.cs | 1098 +- MelonLoader/MelonColorAttribute.cs | 51 +- MelonLoader/MelonCompatibilityLayer.cs | 113 +- MelonLoader/MelonCoroutines.cs | 47 +- MelonLoader/MelonDebug.cs | 83 +- MelonLoader/MelonEvent.cs | 372 +- MelonLoader/MelonEvents.cs | 193 +- MelonLoader/MelonGameAttribute.cs | 89 +- MelonLoader/MelonGameVersionAttribute.cs | 29 +- MelonLoader/MelonHandler.cs | 212 +- MelonLoader/MelonIDAttribute.cs | 21 +- .../MelonIncompatibleAssembliesAttribute.cs | 19 +- MelonLoader/MelonInfoAttribute.cs | 149 +- MelonLoader/MelonLaunchOptions.cs | 328 +- MelonLoader/MelonLogger.cs | 429 +- MelonLoader/MelonMod.cs | 174 +- .../MelonOptionalDependenciesAttribute.cs | 19 +- MelonLoader/MelonPlatformAttribute.cs | 33 +- MelonLoader/MelonPlatformDomainAttribute.cs | 33 +- MelonLoader/MelonPlugin.cs | 146 +- MelonLoader/MelonPreferences.cs | 578 +- MelonLoader/MelonPreferences_Category.cs | 283 +- MelonLoader/MelonPreferences_Entry.cs | 186 +- MelonLoader/MelonPriorityAttribute.cs | 19 +- MelonLoader/MelonProcessAttribute.cs | 45 +- MelonLoader/MelonTypeBase.cs | 69 +- MelonLoader/MelonUtils.cs | 1006 +- MelonLoader/Melons/MelonFolderHandler.cs | 335 +- MelonLoader/Melons/MelonPreprocessor.cs | 139 +- MelonLoader/Modules/MelonModule.cs | 190 +- MelonLoader/NativeLibrary.cs | 197 +- MelonLoader/NativeUtils/CppUtils.cs | 204 +- MelonLoader/NativeUtils/NativeHooks.cs | 198 +- .../PEParser/ImageDataDirectory.cs | 17 +- .../PEParser/ImageExportDirectory.cs | 53 +- .../NativeUtils/PEParser/ImageFileHeader.cs | 21 +- .../NativeUtils/PEParser/ImageNtHeaders.cs | 25 +- .../PEParser/ImageResourceDirectory.cs | 33 +- .../PEParser/ImageSectionHeader.cs | 51 +- .../NativeUtils/PEParser/ImageThunkData32.cs | 25 +- .../NativeUtils/PEParser/ImageThunkData64.cs | 25 +- .../PEParser/OptionalFileHeader32.cs | 17 +- .../PEParser/OptionalFileHeader64.cs | 17 +- MelonLoader/NativeUtils/PEParser/PEUtils.cs | 138 +- MelonLoader/Pastel/Pastel.cs | 302 +- MelonLoader/PatchShield.cs | 114 +- MelonLoader/Preferences/IO/File.cs | 343 +- MelonLoader/Preferences/IO/Watcher.cs | 113 +- .../MelonPreferences_ReflectiveCategory.cs | 212 +- MelonLoader/Preferences/ValueValidator.cs | 69 +- MelonLoader/Properties/BuildInfo.cs | 4 +- MelonLoader/RegisterTypeInIl2Cpp.cs | 79 +- .../RegisterTypeInIl2CppWithInterfaces.cs | 129 +- MelonLoader/ResolvedMelons.cs | 19 +- MelonLoader/Resolver/AssemblyManager.cs | 122 +- MelonLoader/Resolver/AssemblyResolveInfo.cs | 60 +- MelonLoader/Resolver/MelonAssemblyResolver.cs | 201 +- .../Resolver/SearchDirectoryManager.cs | 159 +- MelonLoader/RottenMelon.cs | 33 +- MelonLoader/Semver/IntExtensions.cs | 60 +- MelonLoader/Semver/SemVersion.cs | 984 +- MelonLoader/SupportModule.cs | 173 +- MelonLoader/SupportModule_From.cs | 60 +- MelonLoader/TinyJSON/Decoder.cs | 735 +- MelonLoader/TinyJSON/EncodeOptions.cs | 23 +- MelonLoader/TinyJSON/Encoder.cs | 1177 +- MelonLoader/TinyJSON/Extensions.cs | 45 +- MelonLoader/TinyJSON/JSON.cs | 1261 ++- MelonLoader/TinyJSON/ProxyArray.cs | 186 +- MelonLoader/TinyJSON/ProxyBoolean.cs | 36 +- MelonLoader/TinyJSON/ProxyNumber.cs | 273 +- MelonLoader/TinyJSON/ProxyObject.cs | 168 +- MelonLoader/TinyJSON/ProxyString.cs | 27 +- MelonLoader/TinyJSON/Variant.cs | 454 +- MelonLoader/TomlMapper.cs | 43 +- MelonLoader/Utils/Assertion.cs | 38 +- MelonLoader/Utils/LoggerUtils.cs | 99 +- MelonLoader/Utils/MelonEnvironment.cs | 95 +- MelonLoader/Utils/MonoLibrary.cs | 65 +- MelonLoader/Utils/SteamManifestReader.cs | 173 +- MelonLoader/VerifyLoaderBuildAttribute.cs | 23 +- MelonLoader/VerifyLoaderVersionAttribute.cs | 6 +- 218 files changed, 41057 insertions(+), 42016 deletions(-) diff --git a/MelonLoader/Assertions/LemonAssert.cs b/MelonLoader/Assertions/LemonAssert.cs index ac12bba2c..b03f801a2 100644 --- a/MelonLoader/Assertions/LemonAssert.cs +++ b/MelonLoader/Assertions/LemonAssert.cs @@ -1,121 +1,120 @@ using System; -namespace MelonLoader.Assertions +namespace MelonLoader.Assertions; + +public static class LemonAssert { - public static class LemonAssert + private static void Failure(string exceptionMsg, string userMessage, bool shouldThrowException) { - private static void Failure(string exceptionMsg, string userMessage, bool shouldThrowException) - { - if (string.IsNullOrEmpty(exceptionMsg)) - exceptionMsg = "Assertion has failed!"; - LemonAssertException exception = new LemonAssertException(exceptionMsg, userMessage); - if (shouldThrowException) - throw exception; - else - MelonLogger.Error(exception); - } + if (string.IsNullOrEmpty(exceptionMsg)) + exceptionMsg = "Assertion has failed!"; + var exception = new LemonAssertException(exceptionMsg, userMessage); + if (shouldThrowException) + throw exception; + else + MelonLogger.Error(exception); + } - public static void IsNull(T obj) - => IsNull(obj, null, true); - public static void IsNull(T obj, string userMessage) - => IsNull(obj, userMessage, true); - public static void IsNull(T obj, string userMessage, bool shouldThrowException) - { - bool result = false; - if (LemonAssertMapping.IsNull.TryGetValue(typeof(T), out Delegate method)) - result = (bool)method.DynamicInvoke(new object[] { obj }); - else if (LemonAssertMapping.IsNull.TryGetValue(typeof(object), out Delegate method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj }); - if (!result) - Failure(NullFailureMessage(true), userMessage, shouldThrowException); - } + public static void IsNull(T obj) + => IsNull(obj, null, true); + public static void IsNull(T obj, string userMessage) + => IsNull(obj, userMessage, true); + public static void IsNull(T obj, string userMessage, bool shouldThrowException) + { + var result = false; + if (LemonAssertMapping.IsNull.TryGetValue(typeof(T), out var method)) + result = (bool)method.DynamicInvoke(new object[] { obj }); + else if (LemonAssertMapping.IsNull.TryGetValue(typeof(object), out var method2)) + result = (bool)method2.DynamicInvoke(new object[] { obj }); + if (!result) + Failure(NullFailureMessage(true), userMessage, shouldThrowException); + } - public static void IsNotNull(T obj) - => IsNotNull(obj, null, true); - public static void IsNotNull(T obj, string userMessage) - => IsNotNull(obj, userMessage, true); - public static void IsNotNull(T obj, string userMessage, bool shouldThrowException) - { - bool result = false; - if (LemonAssertMapping.IsNull.TryGetValue(typeof(T), out Delegate method)) - result = (bool)method.DynamicInvoke(new object[] { obj }); - else if (LemonAssertMapping.IsNull.TryGetValue(typeof(object), out Delegate method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj }); - if (result) - Failure(NullFailureMessage(false), userMessage, shouldThrowException); - } + public static void IsNotNull(T obj) + => IsNotNull(obj, null, true); + public static void IsNotNull(T obj, string userMessage) + => IsNotNull(obj, userMessage, true); + public static void IsNotNull(T obj, string userMessage, bool shouldThrowException) + { + var result = false; + if (LemonAssertMapping.IsNull.TryGetValue(typeof(T), out var method)) + result = (bool)method.DynamicInvoke(new object[] { obj }); + else if (LemonAssertMapping.IsNull.TryGetValue(typeof(object), out var method2)) + result = (bool)method2.DynamicInvoke(new object[] { obj }); + if (result) + Failure(NullFailureMessage(false), userMessage, shouldThrowException); + } - public static void IsFalse(bool obj) - => IsFalse(obj, null, true); - public static void IsFalse(bool obj, string userMessage) - => IsFalse(obj, userMessage, true); - public static void IsFalse(bool obj, string userMessage, bool shouldThrowException) - { - if (!obj) - return; - Failure(BooleanFailureMessage(false), userMessage, shouldThrowException); - } + public static void IsFalse(bool obj) + => IsFalse(obj, null, true); + public static void IsFalse(bool obj, string userMessage) + => IsFalse(obj, userMessage, true); + public static void IsFalse(bool obj, string userMessage, bool shouldThrowException) + { + if (!obj) + return; + Failure(BooleanFailureMessage(false), userMessage, shouldThrowException); + } - public static void IsTrue(bool obj) - => IsTrue(obj, null, true); - public static void IsTrue(bool obj, string userMessage) - => IsTrue(obj, userMessage, true); - public static void IsTrue(bool obj, string userMessage, bool shouldThrowException) - { - if (obj) - return; - Failure(BooleanFailureMessage(true), userMessage, shouldThrowException); - } + public static void IsTrue(bool obj) + => IsTrue(obj, null, true); + public static void IsTrue(bool obj, string userMessage) + => IsTrue(obj, userMessage, true); + public static void IsTrue(bool obj, string userMessage, bool shouldThrowException) + { + if (obj) + return; + Failure(BooleanFailureMessage(true), userMessage, shouldThrowException); + } - public static void IsEqual(T obj, T obj2) - => IsEqual(obj, obj2, null, true); - public static void IsEqual(T obj, T obj2, string userMessage) - => IsEqual(obj, obj2, userMessage, true); - public static void IsEqual(T obj, T obj2, string userMessage, bool shouldThrowException) - { - bool result = false; - if (LemonAssertMapping.IsEqual.TryGetValue(typeof(T), out Delegate method)) - result = (bool)method.DynamicInvoke(new object[] { obj, obj2 }); - else if (LemonAssertMapping.IsEqual.TryGetValue(typeof(object), out Delegate method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj, obj2 }); - if (!result) - Failure(EqualityFailureMessage(obj, obj2, true), userMessage, shouldThrowException); - } + public static void IsEqual(T obj, T obj2) + => IsEqual(obj, obj2, null, true); + public static void IsEqual(T obj, T obj2, string userMessage) + => IsEqual(obj, obj2, userMessage, true); + public static void IsEqual(T obj, T obj2, string userMessage, bool shouldThrowException) + { + var result = false; + if (LemonAssertMapping.IsEqual.TryGetValue(typeof(T), out var method)) + result = (bool)method.DynamicInvoke(new object[] { obj, obj2 }); + else if (LemonAssertMapping.IsEqual.TryGetValue(typeof(object), out var method2)) + result = (bool)method2.DynamicInvoke(new object[] { obj, obj2 }); + if (!result) + Failure(EqualityFailureMessage(obj, obj2, true), userMessage, shouldThrowException); + } - public static void IsNotEqual(T obj, T obj2) - => IsNotEqual(obj, obj2, null, true); - public static void IsNotEqual(T obj, T obj2, string userMessage) - => IsNotEqual(obj, obj2, userMessage, true); - public static void IsNotEqual(T obj, T obj2, string userMessage, bool shouldThrowException) - { - bool result = false; - if (LemonAssertMapping.IsEqual.TryGetValue(typeof(T), out Delegate method)) - result = (bool)method.DynamicInvoke(new object[] { obj, obj2 }); - else if (LemonAssertMapping.IsEqual.TryGetValue(typeof(object), out Delegate method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj, obj2 }); - if (result) - Failure(EqualityFailureMessage(obj, obj2, false), userMessage, shouldThrowException); - } + public static void IsNotEqual(T obj, T obj2) + => IsNotEqual(obj, obj2, null, true); + public static void IsNotEqual(T obj, T obj2, string userMessage) + => IsNotEqual(obj, obj2, userMessage, true); + public static void IsNotEqual(T obj, T obj2, string userMessage, bool shouldThrowException) + { + var result = false; + if (LemonAssertMapping.IsEqual.TryGetValue(typeof(T), out var method)) + result = (bool)method.DynamicInvoke(new object[] { obj, obj2 }); + else if (LemonAssertMapping.IsEqual.TryGetValue(typeof(object), out var method2)) + result = (bool)method2.DynamicInvoke(new object[] { obj, obj2 }); + if (result) + Failure(EqualityFailureMessage(obj, obj2, false), userMessage, shouldThrowException); + } - private static string GetFailureMessage(string result, string expected) - => $"Assertion failure! {result}\nExpected: {expected}"; - private static string NullFailureMessage(bool expectedResult) - { - string result = $"Value was {(expectedResult ? "not " : "")}Null"; - string expected = $"Value was {(!expectedResult ? "not " : "")}Null"; - return GetFailureMessage(result, expected); - } - private static string BooleanFailureMessage(bool expectedResult) - { - string result = $"Value was {!expectedResult}"; - string expected = $"Value was {expectedResult}"; - return GetFailureMessage(result, expected); - } - private static string EqualityFailureMessage(object obj, object obj2, bool expectedResult) - { - string result = $"{obj} {(!expectedResult ? "==" : "!=")} {obj2}"; - string expected = $"{obj} {(expectedResult ? "==" : "!=")} {obj2}"; - return GetFailureMessage(result, expected); - } - } + private static string GetFailureMessage(string result, string expected) + => $"Assertion failure! {result}\nExpected: {expected}"; + private static string NullFailureMessage(bool expectedResult) + { + var result = $"Value was {(expectedResult ? "not " : "")}Null"; + var expected = $"Value was {(!expectedResult ? "not " : "")}Null"; + return GetFailureMessage(result, expected); + } + private static string BooleanFailureMessage(bool expectedResult) + { + var result = $"Value was {!expectedResult}"; + var expected = $"Value was {expectedResult}"; + return GetFailureMessage(result, expected); + } + private static string EqualityFailureMessage(object obj, object obj2, bool expectedResult) + { + var result = $"{obj} {(!expectedResult ? "==" : "!=")} {obj2}"; + var expected = $"{obj} {(expectedResult ? "==" : "!=")} {obj2}"; + return GetFailureMessage(result, expected); + } } diff --git a/MelonLoader/Assertions/LemonAssertException.cs b/MelonLoader/Assertions/LemonAssertException.cs index 01a226343..7cfe0499d 100644 --- a/MelonLoader/Assertions/LemonAssertException.cs +++ b/MelonLoader/Assertions/LemonAssertException.cs @@ -1,22 +1,19 @@ using System; -namespace MelonLoader.Assertions +namespace MelonLoader.Assertions; + +public class LemonAssertException : Exception { - public class LemonAssertException : Exception - { - private string UserMessage; + private readonly string UserMessage; - public LemonAssertException(string exceptionMsg, string userMessage) : base(exceptionMsg) - => UserMessage = userMessage; + public LemonAssertException(string exceptionMsg, string userMessage) : base(exceptionMsg) + => UserMessage = userMessage; - public override string Message - { - get - { - if (!string.IsNullOrEmpty(UserMessage)) - return $"{base.Message}\n{UserMessage}"; - return base.Message; - } - } - } + public override string Message + { + get + { + return !string.IsNullOrEmpty(UserMessage) ? $"{base.Message}\n{UserMessage}" : base.Message; + } + } } diff --git a/MelonLoader/Assertions/LemonAssertMapping.cs b/MelonLoader/Assertions/LemonAssertMapping.cs index b39b596db..f222f2d5f 100644 --- a/MelonLoader/Assertions/LemonAssertMapping.cs +++ b/MelonLoader/Assertions/LemonAssertMapping.cs @@ -1,46 +1,43 @@ using System; using System.Collections.Generic; -namespace MelonLoader.Assertions +namespace MelonLoader.Assertions; + +public static class LemonAssertMapping { - public static class LemonAssertMapping - { - internal static Dictionary IsNull = new Dictionary(); - internal static Dictionary IsEqual = new Dictionary(); + internal static Dictionary IsNull = []; + internal static Dictionary IsEqual = []; - internal static void Setup() - { - Register_IsNull(IsNull_object); - Register_IsNull(IsNull_string); - Register_IsEqual(IsEqual_object); - } + internal static void Setup() + { + Register_IsNull(IsNull_object); + Register_IsNull(IsNull_string); + Register_IsEqual(IsEqual_object); + } - public static void Register_IsNull(Func method) - => Register(method, ref IsNull); - public static void Register_IsEqual(Func method) - => Register(method, ref IsEqual); - private static void Register(Delegate method, ref Dictionary tbl) - { - if (method == null) - throw new NullReferenceException(nameof(method)); - Type inputType = typeof(T); - if (tbl.ContainsKey(inputType)) - return; - lock (tbl) - tbl[inputType] = method; - } + public static void Register_IsNull(Func method) + => Register(method, ref IsNull); + public static void Register_IsEqual(Func method) + => Register(method, ref IsEqual); + private static void Register(Delegate method, ref Dictionary tbl) + { + if (method == null) + throw new NullReferenceException(nameof(method)); + var inputType = typeof(T); + if (tbl.ContainsKey(inputType)) + return; + lock (tbl) + tbl[inputType] = method; + } - private static bool IsNull_object(object obj) - => obj == null; - private static bool IsNull_string(string obj) - => string.IsNullOrEmpty(obj); - private static bool IsEqual_object(object obj, object obj2) - { - if (obj == null) - return obj2 == null; - if (obj2 == null) - return obj == null; - return obj.Equals(obj2); - } + private static bool IsNull_object(object obj) + => obj == null; + private static bool IsNull_string(string obj) + => string.IsNullOrEmpty(obj); + private static bool IsEqual_object(object obj, object obj2) + { + if (obj == null) + return obj2 == null; + return obj2 == null ? obj == null : obj.Equals(obj2); } } diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs b/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs index dad40a063..fac163ecd 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs @@ -1,160 +1,159 @@ using System; -namespace Harmony +namespace Harmony; + +[Obsolete("Harmony.MethodType is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead.")] +public enum MethodType +{ + Normal = HarmonyLib.MethodType.Normal, + Getter = HarmonyLib.MethodType.Getter, + Setter = HarmonyLib.MethodType.Setter, + Constructor = HarmonyLib.MethodType.Constructor, + StaticConstructor = HarmonyLib.MethodType.StaticConstructor +} + +[Obsolete("Harmony.PropertyMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead.")] +public enum PropertyMethod +{ + Getter = HarmonyLib.MethodType.Getter, + Setter = HarmonyLib.MethodType.Setter +} + +[Obsolete("Harmony.ArgumentType is Only Here for Compatibility Reasons. Please use HarmonyLib.ArgumentType instead.")] +public enum ArgumentType +{ + Normal = HarmonyLib.ArgumentType.Normal, + Ref = HarmonyLib.ArgumentType.Ref, + Out = HarmonyLib.ArgumentType.Out, + Pointer = HarmonyLib.ArgumentType.Pointer +} + +[Obsolete("Harmony.HarmonyPatchType is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchType instead.")] +public enum HarmonyPatchType +{ + All = HarmonyLib.HarmonyPatchType.All, + Prefix = HarmonyLib.HarmonyPatchType.Prefix, + Postfix = HarmonyLib.HarmonyPatchType.Postfix, + Transpiler = HarmonyLib.HarmonyPatchType.Transpiler +} + +[Obsolete("Harmony.HarmonyAttribute is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAttribute instead.")] +public class HarmonyAttribute : HarmonyLib.HarmonyAttribute { } + +[Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Method, AllowMultiple = true)] +public class HarmonyPatch : HarmonyLib.HarmonyPatch +{ + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch() : base() { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType) : base(declaringType) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, Type[] argumentTypes) : base(declaringType, argumentTypes) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, string methodName) : base(declaringType, methodName) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, string methodName, params Type[] argumentTypes) : base(declaringType, methodName, argumentTypes) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(declaringType, methodName, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, MethodType methodType) : base(declaringType, (HarmonyLib.MethodType)methodType) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, MethodType methodType, params Type[] argumentTypes) : base(declaringType, (HarmonyLib.MethodType)methodType, argumentTypes) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(declaringType, (HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type declaringType, string propertyName, MethodType methodType) : base(declaringType, propertyName, (HarmonyLib.MethodType)methodType) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(string methodName) : base(methodName) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(string methodName, params Type[] argumentTypes) : base(methodName, argumentTypes) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(methodName, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(string propertyName, MethodType methodType) : base(propertyName, (HarmonyLib.MethodType)methodType) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(MethodType methodType) : base((HarmonyLib.MethodType)methodType) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(MethodType methodType, params Type[] argumentTypes) : base((HarmonyLib.MethodType)methodType, argumentTypes) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations) : base((HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type[] argumentTypes) : base(argumentTypes) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(Type[] argumentTypes, ArgumentType[] argumentVariations) : base(argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(string propertyName, PropertyMethod type) : base(propertyName, (HarmonyLib.MethodType)type) { } + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + public HarmonyPatch(string assemblyQualifiedDeclaringType, string methodName, MethodType methodType, Type[] argumentTypes = null, ArgumentType[] argumentVariations = null) : base(assemblyQualifiedDeclaringType, methodName, (HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } +} + +[Obsolete("Harmony.HarmonyPatchAll is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchAll instead.")] +[AttributeUsage(AttributeTargets.Class)] +public class HarmonyPatchAll : HarmonyLib.HarmonyPatchAll { } + +[Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead.")] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class HarmonyPriority : HarmonyLib.HarmonyPriority +{ + [Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead.")] + public HarmonyPriority(int prioritiy) : base(prioritiy) { } +} + +[Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead.")] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class HarmonyBefore : HarmonyLib.HarmonyBefore +{ + [Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead.")] + public HarmonyBefore(params string[] before) : base(before) { } +} + +[Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead.")] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class HarmonyAfter : HarmonyLib.HarmonyAfter +{ + [Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead.")] + public HarmonyAfter(params string[] after) : base(after) { } +} + +[Obsolete("Harmony.HarmonyPrepare is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrepare instead.")] +[AttributeUsage(AttributeTargets.Method)] +public class HarmonyPrepare : HarmonyLib.HarmonyPrepare { } + +[Obsolete("Harmony.HarmonyCleanup is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyCleanup instead.")] +[AttributeUsage(AttributeTargets.Method)] +public class HarmonyCleanup : HarmonyLib.HarmonyCleanup { } + +[Obsolete("Harmony.HarmonyTargetMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethod instead.")] +[AttributeUsage(AttributeTargets.Method)] +public class HarmonyTargetMethod : HarmonyLib.HarmonyTargetMethod { } + +[Obsolete("Harmony.HarmonyTargetMethods is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethods instead.")] +[AttributeUsage(AttributeTargets.Method)] +public class HarmonyTargetMethods : HarmonyLib.HarmonyTargetMethods { } + +[Obsolete("Harmony.HarmonyPrefix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrefix instead.")] +[AttributeUsage(AttributeTargets.Method)] +public class HarmonyPrefix : HarmonyLib.HarmonyPrefix { } + +[Obsolete("Harmony.HarmonyPostfix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPostfix instead.")] +[AttributeUsage(AttributeTargets.Method)] +public class HarmonyPostfix : HarmonyLib.HarmonyPostfix { } + +[Obsolete("Harmony.HarmonyTranspiler is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTranspiler instead.")] +[AttributeUsage(AttributeTargets.Method)] +public class HarmonyTranspiler : HarmonyLib.HarmonyTranspiler { } + +[Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] +public class HarmonyArgument : HarmonyLib.HarmonyArgument { - [Obsolete("Harmony.MethodType is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead.")] - public enum MethodType - { - Normal = HarmonyLib.MethodType.Normal, - Getter = HarmonyLib.MethodType.Getter, - Setter = HarmonyLib.MethodType.Setter, - Constructor = HarmonyLib.MethodType.Constructor, - StaticConstructor = HarmonyLib.MethodType.StaticConstructor - } - - [Obsolete("Harmony.PropertyMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead.")] - public enum PropertyMethod - { - Getter = HarmonyLib.MethodType.Getter, - Setter = HarmonyLib.MethodType.Setter - } - - [Obsolete("Harmony.ArgumentType is Only Here for Compatibility Reasons. Please use HarmonyLib.ArgumentType instead.")] - public enum ArgumentType - { - Normal = HarmonyLib.ArgumentType.Normal, - Ref = HarmonyLib.ArgumentType.Ref, - Out = HarmonyLib.ArgumentType.Out, - Pointer = HarmonyLib.ArgumentType.Pointer - } - - [Obsolete("Harmony.HarmonyPatchType is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchType instead.")] - public enum HarmonyPatchType - { - All = HarmonyLib.HarmonyPatchType.All, - Prefix = HarmonyLib.HarmonyPatchType.Prefix, - Postfix = HarmonyLib.HarmonyPatchType.Postfix, - Transpiler = HarmonyLib.HarmonyPatchType.Transpiler - } - - [Obsolete("Harmony.HarmonyAttribute is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAttribute instead.")] - public class HarmonyAttribute : HarmonyLib.HarmonyAttribute { } - - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Method, AllowMultiple = true)] - public class HarmonyPatch : HarmonyLib.HarmonyPatch - { - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch() : base() { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType) : base(declaringType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, Type[] argumentTypes) : base(declaringType, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, string methodName) : base(declaringType, methodName) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, string methodName, params Type[] argumentTypes) : base(declaringType, methodName, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(declaringType, methodName, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, MethodType methodType) : base(declaringType, (HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, MethodType methodType, params Type[] argumentTypes) : base(declaringType, (HarmonyLib.MethodType)methodType, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(declaringType, (HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type declaringType, string propertyName, MethodType methodType) : base(declaringType, propertyName, (HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(string methodName) : base(methodName) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(string methodName, params Type[] argumentTypes) : base(methodName, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(methodName, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(string propertyName, MethodType methodType) : base(propertyName, (HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(MethodType methodType) : base((HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(MethodType methodType, params Type[] argumentTypes) : base((HarmonyLib.MethodType)methodType, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations) : base((HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type[] argumentTypes) : base(argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(Type[] argumentTypes, ArgumentType[] argumentVariations) : base(argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(string propertyName, PropertyMethod type) : base(propertyName, (HarmonyLib.MethodType)type) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] - public HarmonyPatch(string assemblyQualifiedDeclaringType, string methodName, MethodType methodType, Type[] argumentTypes = null, ArgumentType[] argumentVariations = null) : base(assemblyQualifiedDeclaringType, methodName, (HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - } - - [Obsolete("Harmony.HarmonyPatchAll is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchAll instead.")] - [AttributeUsage(AttributeTargets.Class)] - public class HarmonyPatchAll : HarmonyLib.HarmonyPatchAll { } - - [Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead.")] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class HarmonyPriority : HarmonyLib.HarmonyPriority - { - [Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead.")] - public HarmonyPriority(int prioritiy) : base(prioritiy) { } - } - - [Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead.")] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class HarmonyBefore : HarmonyLib.HarmonyBefore - { - [Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead.")] - public HarmonyBefore(params string[] before) : base(before) { } - } - - [Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead.")] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class HarmonyAfter : HarmonyLib.HarmonyAfter - { - [Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead.")] - public HarmonyAfter(params string[] after) : base(after) { } - } - - [Obsolete("Harmony.HarmonyPrepare is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrepare instead.")] - [AttributeUsage(AttributeTargets.Method)] - public class HarmonyPrepare : HarmonyLib.HarmonyPrepare { } - - [Obsolete("Harmony.HarmonyCleanup is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyCleanup instead.")] - [AttributeUsage(AttributeTargets.Method)] - public class HarmonyCleanup : HarmonyLib.HarmonyCleanup { } - - [Obsolete("Harmony.HarmonyTargetMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethod instead.")] - [AttributeUsage(AttributeTargets.Method)] - public class HarmonyTargetMethod : HarmonyLib.HarmonyTargetMethod { } - - [Obsolete("Harmony.HarmonyTargetMethods is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethods instead.")] - [AttributeUsage(AttributeTargets.Method)] - public class HarmonyTargetMethods : HarmonyLib.HarmonyTargetMethods { } - - [Obsolete("Harmony.HarmonyPrefix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrefix instead.")] - [AttributeUsage(AttributeTargets.Method)] - public class HarmonyPrefix : HarmonyLib.HarmonyPrefix { } - - [Obsolete("Harmony.HarmonyPostfix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPostfix instead.")] - [AttributeUsage(AttributeTargets.Method)] - public class HarmonyPostfix : HarmonyLib.HarmonyPostfix { } - - [Obsolete("Harmony.HarmonyTranspiler is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTranspiler instead.")] - [AttributeUsage(AttributeTargets.Method)] - public class HarmonyTranspiler : HarmonyLib.HarmonyTranspiler { } - - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] - public class HarmonyArgument : HarmonyLib.HarmonyArgument - { - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] - public HarmonyArgument(string originalName) : base(originalName, null) { } - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] - public HarmonyArgument(int index) : base(index, null) { } - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] - public HarmonyArgument(string originalName, string newName) : base(originalName, newName) { } - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] - public HarmonyArgument(int index, string name) : base(index, name) { } - } + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + public HarmonyArgument(string originalName) : base(originalName, null) { } + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + public HarmonyArgument(int index) : base(index, null) { } + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + public HarmonyArgument(string originalName, string newName) : base(originalName, newName) { } + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + public HarmonyArgument(int index, string name) : base(index, name) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Extras/DelegateTypeFactory.cs b/MelonLoader/BackwardsCompatibility/Harmony/Extras/DelegateTypeFactory.cs index 85700343e..8ada545c0 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Extras/DelegateTypeFactory.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Extras/DelegateTypeFactory.cs @@ -1,4 +1,3 @@ -namespace Harmony -{ - public class DelegateTypeFactory : HarmonyLib.DelegateTypeFactory { } -} \ No newline at end of file +namespace Harmony; + +public class DelegateTypeFactory : HarmonyLib.DelegateTypeFactory { } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs index 80ed0327c..c2f9389af 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs @@ -1,97 +1,96 @@ -using System; +using MonoMod.Utils; +using System; using System.Reflection; using System.Reflection.Emit; -using MonoMod.Utils; -namespace Harmony -{ - public delegate object GetterHandler(object source); - public delegate void SetterHandler(object source, object value); - public delegate object InstantiationHandler(); +namespace Harmony; + +public delegate object GetterHandler(object source); +public delegate void SetterHandler(object source, object value); +public delegate object InstantiationHandler(); - public class FastAccess - { - [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true))")] - public static InstantiationHandler CreateInstantiationHandler(Type type) - { - var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null); - if (constructorInfo is null) - throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type)); - var dynamicMethod = new DynamicMethodDefinition($"InstantiateObject_{type.Name}", type, null); - var generator = dynamicMethod.GetILGenerator(); - generator.Emit(OpCodes.Newobj, constructorInfo); - generator.Emit(OpCodes.Ret); - return (InstantiationHandler)dynamicMethod.Generate().CreateDelegate(typeof(InstantiationHandler)); - } +public class FastAccess +{ + [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true))")] + public static InstantiationHandler CreateInstantiationHandler(Type type) + { + var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null); + if (constructorInfo is null) + throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type)); + var dynamicMethod = new DynamicMethodDefinition($"InstantiateObject_{type.Name}", type, null); + var generator = dynamicMethod.GetILGenerator(); + generator.Emit(OpCodes.Newobj, constructorInfo); + generator.Emit(OpCodes.Ret); + return (InstantiationHandler)dynamicMethod.Generate().CreateDelegate(typeof(InstantiationHandler)); + } - [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true))")] - public static GetterHandler CreateGetterHandler(PropertyInfo propertyInfo) - { - var getMethodInfo = propertyInfo.GetGetMethod(true); - var dynamicGet = CreateGetDynamicMethod(propertyInfo.DeclaringType); - var getGenerator = dynamicGet.GetILGenerator(); - getGenerator.Emit(OpCodes.Ldarg_0); - getGenerator.Emit(OpCodes.Call, getMethodInfo); - getGenerator.Emit(OpCodes.Ret); - return (GetterHandler)dynamicGet.Generate().CreateDelegate(typeof(GetterHandler)); - } + [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true))")] + public static GetterHandler CreateGetterHandler(PropertyInfo propertyInfo) + { + var getMethodInfo = propertyInfo.GetGetMethod(true); + var dynamicGet = CreateGetDynamicMethod(propertyInfo.DeclaringType); + var getGenerator = dynamicGet.GetILGenerator(); + getGenerator.Emit(OpCodes.Ldarg_0); + getGenerator.Emit(OpCodes.Call, getMethodInfo); + getGenerator.Emit(OpCodes.Ret); + return (GetterHandler)dynamicGet.Generate().CreateDelegate(typeof(GetterHandler)); + } - [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo)")] - public static GetterHandler CreateGetterHandler(FieldInfo fieldInfo) - { - var dynamicGet = CreateGetDynamicMethod(fieldInfo.DeclaringType); - var getGenerator = dynamicGet.GetILGenerator(); - getGenerator.Emit(OpCodes.Ldarg_0); - getGenerator.Emit(OpCodes.Ldfld, fieldInfo); - getGenerator.Emit(OpCodes.Ret); - return (GetterHandler)dynamicGet.Generate().CreateDelegate(typeof(GetterHandler)); - } + [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo)")] + public static GetterHandler CreateGetterHandler(FieldInfo fieldInfo) + { + var dynamicGet = CreateGetDynamicMethod(fieldInfo.DeclaringType); + var getGenerator = dynamicGet.GetILGenerator(); + getGenerator.Emit(OpCodes.Ldarg_0); + getGenerator.Emit(OpCodes.Ldfld, fieldInfo); + getGenerator.Emit(OpCodes.Ret); + return (GetterHandler)dynamicGet.Generate().CreateDelegate(typeof(GetterHandler)); + } - [Obsolete("Use AccessTools.FieldRefAccess(name) for fields and " + - "AccessTools.MethodDelegate>(AccessTools.PropertyGetter(typeof(T), name)) for properties")] - public static GetterHandler CreateFieldGetter(Type type, params string[] names) - { - foreach (var name in names) - { - var field = type.GetField(name, AccessTools.all); - if (field is object) - return CreateGetterHandler(field); - var property = type.GetProperty(name, AccessTools.all); - if (property is object) - return CreateGetterHandler(property); - } - return null; - } + [Obsolete("Use AccessTools.FieldRefAccess(name) for fields and " + + "AccessTools.MethodDelegate>(AccessTools.PropertyGetter(typeof(T), name)) for properties")] + public static GetterHandler CreateFieldGetter(Type type, params string[] names) + { + foreach (var name in names) + { + var field = type.GetField(name, AccessTools.all); + if (field is not null) + return CreateGetterHandler(field); + var property = type.GetProperty(name, AccessTools.all); + if (property is not null) + return CreateGetterHandler(property); + } + return null; + } - [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetSetMethod(true))")] - public static SetterHandler CreateSetterHandler(PropertyInfo propertyInfo) - { - var setMethodInfo = propertyInfo.GetSetMethod(true); - var dynamicSet = CreateSetDynamicMethod(propertyInfo.DeclaringType); - var setGenerator = dynamicSet.GetILGenerator(); - setGenerator.Emit(OpCodes.Ldarg_0); - setGenerator.Emit(OpCodes.Ldarg_1); - setGenerator.Emit(OpCodes.Call, setMethodInfo); - setGenerator.Emit(OpCodes.Ret); - return (SetterHandler)dynamicSet.Generate().CreateDelegate(typeof(SetterHandler)); - } + [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetSetMethod(true))")] + public static SetterHandler CreateSetterHandler(PropertyInfo propertyInfo) + { + var setMethodInfo = propertyInfo.GetSetMethod(true); + var dynamicSet = CreateSetDynamicMethod(propertyInfo.DeclaringType); + var setGenerator = dynamicSet.GetILGenerator(); + setGenerator.Emit(OpCodes.Ldarg_0); + setGenerator.Emit(OpCodes.Ldarg_1); + setGenerator.Emit(OpCodes.Call, setMethodInfo); + setGenerator.Emit(OpCodes.Ret); + return (SetterHandler)dynamicSet.Generate().CreateDelegate(typeof(SetterHandler)); + } - [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo)")] - public static SetterHandler CreateSetterHandler(FieldInfo fieldInfo) - { - var dynamicSet = CreateSetDynamicMethod(fieldInfo.DeclaringType); - var setGenerator = dynamicSet.GetILGenerator(); - setGenerator.Emit(OpCodes.Ldarg_0); - setGenerator.Emit(OpCodes.Ldarg_1); - setGenerator.Emit(OpCodes.Stfld, fieldInfo); - setGenerator.Emit(OpCodes.Ret); - return (SetterHandler)dynamicSet.Generate().CreateDelegate(typeof(SetterHandler)); - } + [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo)")] + public static SetterHandler CreateSetterHandler(FieldInfo fieldInfo) + { + var dynamicSet = CreateSetDynamicMethod(fieldInfo.DeclaringType); + var setGenerator = dynamicSet.GetILGenerator(); + setGenerator.Emit(OpCodes.Ldarg_0); + setGenerator.Emit(OpCodes.Ldarg_1); + setGenerator.Emit(OpCodes.Stfld, fieldInfo); + setGenerator.Emit(OpCodes.Ret); + return (SetterHandler)dynamicSet.Generate().CreateDelegate(typeof(SetterHandler)); + } - static DynamicMethodDefinition CreateGetDynamicMethod(Type type) - => new DynamicMethodDefinition($"DynamicGet_{type.Name}", typeof(object), new Type[] { typeof(object) }); + private static DynamicMethodDefinition CreateGetDynamicMethod(Type type) + => new($"DynamicGet_{type.Name}", typeof(object), new Type[] { typeof(object) }); - static DynamicMethodDefinition CreateSetDynamicMethod(Type type) - => new DynamicMethodDefinition($"DynamicSet_{type.Name}", typeof(void), new Type[] { typeof(object), typeof(object) }); - } + private static DynamicMethodDefinition CreateSetDynamicMethod(Type type) + => new($"DynamicSet_{type.Name}", typeof(void), new Type[] { typeof(object), typeof(object) }); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Extras/MethodInvoker.cs b/MelonLoader/BackwardsCompatibility/Harmony/Extras/MethodInvoker.cs index 34e49109e..55fa637c3 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Extras/MethodInvoker.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Extras/MethodInvoker.cs @@ -2,16 +2,15 @@ using System.Reflection; using System.Reflection.Emit; -namespace Harmony +namespace Harmony; + +public delegate object FastInvokeHandler(object target, object[] paramters); +public class MethodInvoker { - public delegate object FastInvokeHandler(object target, object[] paramters); - public class MethodInvoker - { - public static FastInvokeHandler GetHandler(DynamicMethod methodInfo, Module module) => - ConvertFastInvokeHandler(HarmonyLib.MethodInvoker.GetHandler(methodInfo)); - public static FastInvokeHandler GetHandler(MethodInfo methodInfo) => - ConvertFastInvokeHandler(HarmonyLib.MethodInvoker.GetHandler(methodInfo)); - private static FastInvokeHandler ConvertFastInvokeHandler(HarmonyLib.FastInvokeHandler sourceDelegate) => - (FastInvokeHandler)Delegate.CreateDelegate(typeof(FastInvokeHandler), sourceDelegate.Target, sourceDelegate.Method); - } + public static FastInvokeHandler GetHandler(DynamicMethod methodInfo, Module module) => + ConvertFastInvokeHandler(HarmonyLib.MethodInvoker.GetHandler(methodInfo)); + public static FastInvokeHandler GetHandler(MethodInfo methodInfo) => + ConvertFastInvokeHandler(HarmonyLib.MethodInvoker.GetHandler(methodInfo)); + private static FastInvokeHandler ConvertFastInvokeHandler(HarmonyLib.FastInvokeHandler sourceDelegate) => + (FastInvokeHandler)Delegate.CreateDelegate(typeof(FastInvokeHandler), sourceDelegate.Target, sourceDelegate.Method); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs index c72b80207..809ae5ae9 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs @@ -3,27 +3,25 @@ using System.Reflection.Emit; #pragma warning disable 0618 -namespace Harmony +namespace Harmony; + +public class HarmonyInstance : HarmonyLib.Harmony { - public class HarmonyInstance : HarmonyLib.Harmony - { - [Obsolete("Harmony.HarmonyInstance is obsolete. Please use HarmonyLib.Harmony instead.")] - public HarmonyInstance(string id) : base(id) { } + [Obsolete("Harmony.HarmonyInstance is obsolete. Please use HarmonyLib.Harmony instead.")] + public HarmonyInstance(string id) : base(id) { } - [Obsolete("Harmony.HarmonyInstance.Create is obsolete. Please use the HarmonyLib.Harmony Constructor instead.")] - public static HarmonyInstance Create(string id) - { - if (id == null) throw new Exception("id cannot be null"); - return new HarmonyInstance(id); - } + [Obsolete("Harmony.HarmonyInstance.Create is obsolete. Please use the HarmonyLib.Harmony Constructor instead.")] + public static HarmonyInstance Create(string id) + { + return id == null ? throw new Exception("id cannot be null") : new HarmonyInstance(id); + } - [Obsolete("Harmony.HarmonyInstance.Patch is obsolete. Please use HarmonyLib.Harmony.Patch instead.")] - public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) - { - base.Patch(original, prefix, postfix, transpiler); - return null; - } + [Obsolete("Harmony.HarmonyInstance.Patch is obsolete. Please use HarmonyLib.Harmony.Patch instead.")] + public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + { + base.Patch(original, prefix, postfix, transpiler); + return null; + } - public void Unpatch(MethodBase original, HarmonyPatchType type, string harmonyID = null) => Unpatch(original, (HarmonyLib.HarmonyPatchType)type, harmonyID); - } + public void Unpatch(MethodBase original, HarmonyPatchType type, string harmonyID = null) => Unpatch(original, (HarmonyLib.HarmonyPatchType)type, harmonyID); } diff --git a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs index a6c8b95a8..ff3b6b192 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs @@ -3,36 +3,35 @@ using System.Linq; using System.Reflection; -namespace Harmony +namespace Harmony; + +[Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] +public class HarmonyMethod : HarmonyLib.HarmonyMethod { - [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] - public class HarmonyMethod : HarmonyLib.HarmonyMethod - { - [Obsolete("Harmony.HarmonyMethod.prioritiy is obsolete. Please use HarmonyLib.HarmonyMethod.priority instead.")] - public int prioritiy = -1; - [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] - public HarmonyMethod() : base() { } - [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] - public HarmonyMethod(MethodInfo method) : base(method) { } - [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] - public HarmonyMethod(Type type, string name, Type[] parameters = null) : base(type, name, parameters) { } - [Obsolete("Harmony.HarmonyMethod.Merge is obsolete. Please use HarmonyLib.HarmonyMethod.Merge instead.")] - public static HarmonyMethod Merge(List attributes) => (HarmonyMethod)Merge(Array.ConvertAll(attributes.ToArray(), x => (HarmonyLib.HarmonyMethod)x).ToList()); - public override string ToString() => base.ToString(); - } + [Obsolete("Harmony.HarmonyMethod.prioritiy is obsolete. Please use HarmonyLib.HarmonyMethod.priority instead.")] + public int prioritiy = -1; + [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] + public HarmonyMethod() : base() { } + [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] + public HarmonyMethod(MethodInfo method) : base(method) { } + [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] + public HarmonyMethod(Type type, string name, Type[] parameters = null) : base(type, name, parameters) { } + [Obsolete("Harmony.HarmonyMethod.Merge is obsolete. Please use HarmonyLib.HarmonyMethod.Merge instead.")] + public static HarmonyMethod Merge(List attributes) => (HarmonyMethod)Merge(Array.ConvertAll(attributes.ToArray(), x => (HarmonyLib.HarmonyMethod)x).ToList()); + public override string ToString() => base.ToString(); +} - [Obsolete("Harmony.HarmonyMethodExtensions is obsolete. Please use HarmonyLib.HarmonyMethodExtensions instead.")] - public static class HarmonyMethodExtensions - { - [Obsolete("Harmony.HarmonyMethodExtensions.CopyTo is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.CopyTo instead.")] - public static void CopyTo(this HarmonyMethod from, HarmonyMethod to) => HarmonyLib.HarmonyMethodExtensions.CopyTo(from, to); - [Obsolete("Harmony.HarmonyMethodExtensions.Clone is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Clone instead.")] - public static HarmonyMethod Clone(this HarmonyMethod original) => (HarmonyMethod)HarmonyLib.HarmonyMethodExtensions.Clone(original); - [Obsolete("Harmony.HarmonyMethodExtensions.Merge is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Merge instead.")] - public static HarmonyMethod Merge(this HarmonyMethod master, HarmonyMethod detail) => (HarmonyMethod)HarmonyLib.HarmonyMethodExtensions.Merge(master, detail); - [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(Type) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromType instead.")] - public static List GetHarmonyMethods(this Type type) => Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromType(type).ToArray(), x => (HarmonyMethod)x).ToList(); - [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(MethodBase) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromMethod instead.")] - public static List GetHarmonyMethods(this MethodBase method) => Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromMethod(method).ToArray(), x => (HarmonyMethod)x).ToList(); - } +[Obsolete("Harmony.HarmonyMethodExtensions is obsolete. Please use HarmonyLib.HarmonyMethodExtensions instead.")] +public static class HarmonyMethodExtensions +{ + [Obsolete("Harmony.HarmonyMethodExtensions.CopyTo is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.CopyTo instead.")] + public static void CopyTo(this HarmonyMethod from, HarmonyMethod to) => HarmonyLib.HarmonyMethodExtensions.CopyTo(from, to); + [Obsolete("Harmony.HarmonyMethodExtensions.Clone is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Clone instead.")] + public static HarmonyMethod Clone(this HarmonyMethod original) => (HarmonyMethod)HarmonyLib.HarmonyMethodExtensions.Clone(original); + [Obsolete("Harmony.HarmonyMethodExtensions.Merge is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Merge instead.")] + public static HarmonyMethod Merge(this HarmonyMethod master, HarmonyMethod detail) => (HarmonyMethod)HarmonyLib.HarmonyMethodExtensions.Merge(master, detail); + [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(Type) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromType instead.")] + public static List GetHarmonyMethods(this Type type) => Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromType(type).ToArray(), x => (HarmonyMethod)x).ToList(); + [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(MethodBase) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromMethod instead.")] + public static List GetHarmonyMethods(this MethodBase method) => Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromMethod(method).ToArray(), x => (HarmonyMethod)x).ToList(); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs b/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs index 3b20e79b2..4142d3645 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs @@ -1,45 +1,44 @@ -using System; +using MonoMod.Utils; +using System; using System.Reflection; -using MonoMod.Utils; -namespace Harmony +namespace Harmony; + +[Obsolete("Harmony.PatchInfoSerialization is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization instead.")] +public static class PatchInfoSerialization { - [Obsolete("Harmony.PatchInfoSerialization is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization instead.")] - public static class PatchInfoSerialization - { - private delegate HarmonyLib.PatchInfo HarmonyLib_PatchInfoSerialization_Deserialize_Delegate(byte[] bytes); - private static HarmonyLib_PatchInfoSerialization_Deserialize_Delegate HarmonyLib_PatchInfoSerialization_Deserialize - = HarmonyLib.AccessTools.Method("HarmonyLib.PatchInfoSerialization:Deserialize").CreateDelegate(); + private delegate HarmonyLib.PatchInfo HarmonyLib_PatchInfoSerialization_Deserialize_Delegate(byte[] bytes); + private static readonly HarmonyLib_PatchInfoSerialization_Deserialize_Delegate HarmonyLib_PatchInfoSerialization_Deserialize + = HarmonyLib.AccessTools.Method("HarmonyLib.PatchInfoSerialization:Deserialize").CreateDelegate(); - private delegate int HarmonyLib_PatchInfoSerialization_PriorityComparer_Delegate(object obj, int index, int priority); - private static HarmonyLib_PatchInfoSerialization_PriorityComparer_Delegate HarmonyLib_PatchInfoSerialization_PriorityComparer - = HarmonyLib.AccessTools.Method("HarmonyLib.PatchInfoSerialization:PriorityComparer").CreateDelegate(); + private delegate int HarmonyLib_PatchInfoSerialization_PriorityComparer_Delegate(object obj, int index, int priority); + private static readonly HarmonyLib_PatchInfoSerialization_PriorityComparer_Delegate HarmonyLib_PatchInfoSerialization_PriorityComparer + = HarmonyLib.AccessTools.Method("HarmonyLib.PatchInfoSerialization:PriorityComparer").CreateDelegate(); - [Obsolete("Harmony.PatchInfoSerialization.Deserialize is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.Deserialize instead.")] - public static PatchInfo Deserialize(byte[] bytes) => (PatchInfo)HarmonyLib_PatchInfoSerialization_Deserialize(bytes); - [Obsolete("Harmony.PatchInfoSerialization.PriorityComparer is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.PriorityComparer instead.")] - public static int PriorityComparer(object obj, int index, int priority, string[] before, string[] after) => HarmonyLib_PatchInfoSerialization_PriorityComparer(obj, index, priority); - } + [Obsolete("Harmony.PatchInfoSerialization.Deserialize is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.Deserialize instead.")] + public static PatchInfo Deserialize(byte[] bytes) => (PatchInfo)HarmonyLib_PatchInfoSerialization_Deserialize(bytes); + [Obsolete("Harmony.PatchInfoSerialization.PriorityComparer is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.PriorityComparer instead.")] + public static int PriorityComparer(object obj, int index, int priority, string[] before, string[] after) => HarmonyLib_PatchInfoSerialization_PriorityComparer(obj, index, priority); +} - [Obsolete("Harmony.PatchInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfo instead.")] - [Serializable] - public class PatchInfo : HarmonyLib.PatchInfo { } +[Obsolete("Harmony.PatchInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfo instead.")] +[Serializable] +public class PatchInfo : HarmonyLib.PatchInfo { } - [Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead.")] - [Serializable] - public class Patch : IComparable - { - readonly public MethodInfo patch; - private HarmonyLib.Patch patchWrapper; - [Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead.")] - public Patch(MethodInfo patch, int index, string owner, int priority, string[] before, string[] after) - { - this.patch = patch; - patchWrapper = new HarmonyLib.Patch(patch, index, owner, priority, before, after, false); - } - public MethodInfo GetMethod(MethodBase original) => patchWrapper.GetMethod(original); - public override bool Equals(object obj) => patchWrapper.Equals(obj); - public int CompareTo(object obj) => patchWrapper.CompareTo(obj); - public override int GetHashCode() => patchWrapper.GetHashCode(); - } +[Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead.")] +[Serializable] +public class Patch : IComparable +{ + public readonly MethodInfo patch; + private readonly HarmonyLib.Patch patchWrapper; + [Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead.")] + public Patch(MethodInfo patch, int index, string owner, int priority, string[] before, string[] after) + { + this.patch = patch; + patchWrapper = new HarmonyLib.Patch(patch, index, owner, priority, before, after, false); + } + public MethodInfo GetMethod(MethodBase original) => patchWrapper.GetMethod(original); + public override bool Equals(object obj) => patchWrapper.Equals(obj); + public int CompareTo(object obj) => patchWrapper.CompareTo(obj); + public override int GetHashCode() => patchWrapper.GetHashCode(); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs b/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs index 0b04307b9..62d95efcd 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs @@ -1,27 +1,26 @@ using System; -namespace Harmony +namespace Harmony; + +[Obsolete("Harmony.Priority is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority instead.")] +public static class Priority { - [Obsolete("Harmony.Priority is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority instead.")] - public static class Priority - { - [Obsolete("Harmony.Priority.Last is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Last instead.")] - public const int Last = HarmonyLib.Priority.Last; - [Obsolete("Harmony.Priority.VeryLow is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryLow instead.")] - public const int VeryLow = HarmonyLib.Priority.VeryLow; - [Obsolete("Harmony.Priority.Low is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Low instead.")] - public const int Low = HarmonyLib.Priority.Low; - [Obsolete("Harmony.Priority.LowerThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.LowerThanNormal instead.")] - public const int LowerThanNormal = HarmonyLib.Priority.LowerThanNormal; - [Obsolete("Harmony.Priority.Normal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Normal instead.")] - public const int Normal = HarmonyLib.Priority.Normal; - [Obsolete("Harmony.Priority.HigherThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.HigherThanNormal instead.")] - public const int HigherThanNormal = HarmonyLib.Priority.HigherThanNormal; - [Obsolete("Harmony.Priority.High is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.High instead.")] - public const int High = HarmonyLib.Priority.High; - [Obsolete("Harmony.Priority.VeryHigh is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryHigh instead.")] - public const int VeryHigh = HarmonyLib.Priority.VeryHigh; - [Obsolete("Harmony.Priority.First is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.First instead.")] - public const int First = HarmonyLib.Priority.First; - } + [Obsolete("Harmony.Priority.Last is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Last instead.")] + public const int Last = HarmonyLib.Priority.Last; + [Obsolete("Harmony.Priority.VeryLow is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryLow instead.")] + public const int VeryLow = HarmonyLib.Priority.VeryLow; + [Obsolete("Harmony.Priority.Low is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Low instead.")] + public const int Low = HarmonyLib.Priority.Low; + [Obsolete("Harmony.Priority.LowerThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.LowerThanNormal instead.")] + public const int LowerThanNormal = HarmonyLib.Priority.LowerThanNormal; + [Obsolete("Harmony.Priority.Normal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Normal instead.")] + public const int Normal = HarmonyLib.Priority.Normal; + [Obsolete("Harmony.Priority.HigherThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.HigherThanNormal instead.")] + public const int HigherThanNormal = HarmonyLib.Priority.HigherThanNormal; + [Obsolete("Harmony.Priority.High is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.High instead.")] + public const int High = HarmonyLib.Priority.High; + [Obsolete("Harmony.Priority.VeryHigh is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryHigh instead.")] + public const int VeryHigh = HarmonyLib.Priority.VeryHigh; + [Obsolete("Harmony.Priority.First is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.First instead.")] + public const int First = HarmonyLib.Priority.First; } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs b/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs index ef24fae03..8cb9ba2c6 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs @@ -2,52 +2,51 @@ using System.Collections.Generic; using System.Reflection; -namespace Harmony +namespace Harmony; + +[Obsolete("Harmony.AccessTools is Only Here for Compatibility Reasons. Please use HarmonyLib.AccessTools instead.")] +public static class AccessTools { - [Obsolete("Harmony.AccessTools is Only Here for Compatibility Reasons. Please use HarmonyLib.AccessTools instead.")] - public static class AccessTools - { - public static BindingFlags all = HarmonyLib.AccessTools.all; - public static Type TypeByName(string name) => HarmonyLib.AccessTools.TypeByName(name); - public static T FindIncludingBaseTypes(Type type, Func action) where T : class => HarmonyLib.AccessTools.FindIncludingBaseTypes(type, action); - public static T FindIncludingInnerTypes(Type type, Func action) where T : class => HarmonyLib.AccessTools.FindIncludingInnerTypes(type, action); - public static FieldInfo Field(Type type, string name) => HarmonyLib.AccessTools.Field(type, name); - public static FieldInfo Field(Type type, int idx) => HarmonyLib.AccessTools.DeclaredField(type, idx); - public static PropertyInfo DeclaredProperty(Type type, string name) => HarmonyLib.AccessTools.DeclaredProperty(type, name); - public static PropertyInfo Property(Type type, string name) => HarmonyLib.AccessTools.Property(type, name); - public static MethodInfo DeclaredMethod(Type type, string name, Type[] parameters = null, Type[] generics = null) => HarmonyLib.AccessTools.DeclaredMethod(type, name, parameters, generics); - public static MethodInfo Method(Type type, string name, Type[] parameters = null, Type[] generics = null) => HarmonyLib.AccessTools.Method(type, name, parameters, generics); - public static MethodInfo Method(string typeColonMethodname, Type[] parameters = null, Type[] generics = null) => HarmonyLib.AccessTools.Method(typeColonMethodname, parameters, generics); - public static List GetMethodNames(Type type) => HarmonyLib.AccessTools.GetMethodNames(type); - public static List GetMethodNames(object instance) => HarmonyLib.AccessTools.GetMethodNames(instance); - public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) => HarmonyLib.AccessTools.DeclaredConstructor(type, parameters); - public static ConstructorInfo Constructor(Type type, Type[] parameters = null) => HarmonyLib.AccessTools.Constructor(type, parameters); - public static List GetDeclaredConstructors(Type type) => HarmonyLib.AccessTools.GetDeclaredConstructors(type); - public static List GetDeclaredMethods(Type type) => HarmonyLib.AccessTools.GetDeclaredMethods(type); - public static List GetDeclaredProperties(Type type) => HarmonyLib.AccessTools.GetDeclaredProperties(type); - public static List GetDeclaredFields(Type type) => HarmonyLib.AccessTools.GetDeclaredFields(type); - public static Type GetReturnedType(MethodBase method) => HarmonyLib.AccessTools.GetReturnedType(method); - public static Type Inner(Type type, string name) => HarmonyLib.AccessTools.Inner(type, name); - public static Type FirstInner(Type type, Func predicate) => HarmonyLib.AccessTools.FirstInner(type, predicate); - public static MethodInfo FirstMethod(Type type, Func predicate) => HarmonyLib.AccessTools.FirstMethod(type, predicate); - public static ConstructorInfo FirstConstructor(Type type, Func predicate) => HarmonyLib.AccessTools.FirstConstructor(type, predicate); - public static PropertyInfo FirstProperty(Type type, Func predicate) => HarmonyLib.AccessTools.FirstProperty(type, predicate); - public static Type[] GetTypes(object[] parameters) => HarmonyLib.AccessTools.GetTypes(parameters); - public static List GetFieldNames(Type type) => HarmonyLib.AccessTools.GetFieldNames(type); - public static List GetFieldNames(object instance) => HarmonyLib.AccessTools.GetFieldNames(instance); - public static List GetPropertyNames(Type type) => HarmonyLib.AccessTools.GetPropertyNames(type); - public static List GetPropertyNames(object instance) => HarmonyLib.AccessTools.GetPropertyNames(instance); - public delegate ref U FieldRef(T obj); - public static FieldRef FieldRefAccess(string fieldName) => ConvertFieldRef(HarmonyLib.AccessTools.FieldRefAccess(fieldName)); - public static ref U FieldRefAccess(T instance, string fieldName) => ref FieldRefAccess(fieldName)(instance); - private static FieldRef ConvertFieldRef(HarmonyLib.AccessTools.FieldRef sourceDelegate) => - (FieldRef)Delegate.CreateDelegate(typeof(FieldRef), sourceDelegate.Target, sourceDelegate.Method); - public static void ThrowMissingMemberException(Type type, params string[] names) => HarmonyLib.AccessTools.ThrowMissingMemberException(type, names); - public static object GetDefaultValue(Type type) => HarmonyLib.AccessTools.GetDefaultValue(type); - public static object CreateInstance(Type type) => HarmonyLib.AccessTools.CreateInstance(type); - public static bool IsStruct(Type type) => HarmonyLib.AccessTools.IsStruct(type); - public static bool IsClass(Type type) => HarmonyLib.AccessTools.IsClass(type); - public static bool IsValue(Type type) => HarmonyLib.AccessTools.IsValue(type); - public static bool IsVoid(Type type) => HarmonyLib.AccessTools.IsVoid(type); - } + public static BindingFlags all = HarmonyLib.AccessTools.all; + public static Type TypeByName(string name) => HarmonyLib.AccessTools.TypeByName(name); + public static T FindIncludingBaseTypes(Type type, Func action) where T : class => HarmonyLib.AccessTools.FindIncludingBaseTypes(type, action); + public static T FindIncludingInnerTypes(Type type, Func action) where T : class => HarmonyLib.AccessTools.FindIncludingInnerTypes(type, action); + public static FieldInfo Field(Type type, string name) => HarmonyLib.AccessTools.Field(type, name); + public static FieldInfo Field(Type type, int idx) => HarmonyLib.AccessTools.DeclaredField(type, idx); + public static PropertyInfo DeclaredProperty(Type type, string name) => HarmonyLib.AccessTools.DeclaredProperty(type, name); + public static PropertyInfo Property(Type type, string name) => HarmonyLib.AccessTools.Property(type, name); + public static MethodInfo DeclaredMethod(Type type, string name, Type[] parameters = null, Type[] generics = null) => HarmonyLib.AccessTools.DeclaredMethod(type, name, parameters, generics); + public static MethodInfo Method(Type type, string name, Type[] parameters = null, Type[] generics = null) => HarmonyLib.AccessTools.Method(type, name, parameters, generics); + public static MethodInfo Method(string typeColonMethodname, Type[] parameters = null, Type[] generics = null) => HarmonyLib.AccessTools.Method(typeColonMethodname, parameters, generics); + public static List GetMethodNames(Type type) => HarmonyLib.AccessTools.GetMethodNames(type); + public static List GetMethodNames(object instance) => HarmonyLib.AccessTools.GetMethodNames(instance); + public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) => HarmonyLib.AccessTools.DeclaredConstructor(type, parameters); + public static ConstructorInfo Constructor(Type type, Type[] parameters = null) => HarmonyLib.AccessTools.Constructor(type, parameters); + public static List GetDeclaredConstructors(Type type) => HarmonyLib.AccessTools.GetDeclaredConstructors(type); + public static List GetDeclaredMethods(Type type) => HarmonyLib.AccessTools.GetDeclaredMethods(type); + public static List GetDeclaredProperties(Type type) => HarmonyLib.AccessTools.GetDeclaredProperties(type); + public static List GetDeclaredFields(Type type) => HarmonyLib.AccessTools.GetDeclaredFields(type); + public static Type GetReturnedType(MethodBase method) => HarmonyLib.AccessTools.GetReturnedType(method); + public static Type Inner(Type type, string name) => HarmonyLib.AccessTools.Inner(type, name); + public static Type FirstInner(Type type, Func predicate) => HarmonyLib.AccessTools.FirstInner(type, predicate); + public static MethodInfo FirstMethod(Type type, Func predicate) => HarmonyLib.AccessTools.FirstMethod(type, predicate); + public static ConstructorInfo FirstConstructor(Type type, Func predicate) => HarmonyLib.AccessTools.FirstConstructor(type, predicate); + public static PropertyInfo FirstProperty(Type type, Func predicate) => HarmonyLib.AccessTools.FirstProperty(type, predicate); + public static Type[] GetTypes(object[] parameters) => HarmonyLib.AccessTools.GetTypes(parameters); + public static List GetFieldNames(Type type) => HarmonyLib.AccessTools.GetFieldNames(type); + public static List GetFieldNames(object instance) => HarmonyLib.AccessTools.GetFieldNames(instance); + public static List GetPropertyNames(Type type) => HarmonyLib.AccessTools.GetPropertyNames(type); + public static List GetPropertyNames(object instance) => HarmonyLib.AccessTools.GetPropertyNames(instance); + public delegate ref U FieldRef(T obj); + public static FieldRef FieldRefAccess(string fieldName) => ConvertFieldRef(HarmonyLib.AccessTools.FieldRefAccess(fieldName)); + public static ref U FieldRefAccess(T instance, string fieldName) => ref FieldRefAccess(fieldName)(instance); + private static FieldRef ConvertFieldRef(HarmonyLib.AccessTools.FieldRef sourceDelegate) => + (FieldRef)Delegate.CreateDelegate(typeof(FieldRef), sourceDelegate.Target, sourceDelegate.Method); + public static void ThrowMissingMemberException(Type type, params string[] names) => HarmonyLib.AccessTools.ThrowMissingMemberException(type, names); + public static object GetDefaultValue(Type type) => HarmonyLib.AccessTools.GetDefaultValue(type); + public static object CreateInstance(Type type) => HarmonyLib.AccessTools.CreateInstance(type); + public static bool IsStruct(Type type) => HarmonyLib.AccessTools.IsStruct(type); + public static bool IsClass(Type type) => HarmonyLib.AccessTools.IsClass(type); + public static bool IsValue(Type type) => HarmonyLib.AccessTools.IsValue(type); + public static bool IsVoid(Type type) => HarmonyLib.AccessTools.IsVoid(type); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs b/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs index e093b4cc1..771f78db4 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs @@ -2,37 +2,36 @@ using System.Collections.Generic; using System.Reflection; -namespace Harmony +namespace Harmony; + +[Obsolete("Harmony.GeneralExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions instead.")] +public static class GeneralExtensions { - [Obsolete("Harmony.GeneralExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions instead.")] - public static class GeneralExtensions - { - [Obsolete("Harmony.GeneralExtensions.Join is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Join instead.")] - public static string Join(this IEnumerable enumeration, Func converter = null, string delimiter = ", ") => HarmonyLib.GeneralExtensions.Join(enumeration, converter, delimiter); - [Obsolete("Harmony.GeneralExtensions.Description is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Description instead.")] - public static string Description(this Type[] parameters) => HarmonyLib.GeneralExtensions.Description(parameters); - [Obsolete("Harmony.GeneralExtensions.FullDescription is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.FullDescription instead.")] - public static string FullDescription(this MethodBase method) => HarmonyLib.GeneralExtensions.FullDescription(method); - [Obsolete("Harmony.GeneralExtensions.Types is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Types instead.")] - public static Type[] Types(this ParameterInfo[] pinfo) => HarmonyLib.GeneralExtensions.Types(pinfo); - [Obsolete("Harmony.GeneralExtensions.GetValueSafe is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetValueSafe instead.")] - public static T GetValueSafe(this Dictionary dictionary, S key) => HarmonyLib.GeneralExtensions.GetValueSafe(dictionary, key); - [Obsolete("Harmony.GeneralExtensions.GetTypedValue is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetTypedValue instead.")] - public static T GetTypedValue(this Dictionary dictionary, string key) => HarmonyLib.GeneralExtensions.GetTypedValue(dictionary, key); - } + [Obsolete("Harmony.GeneralExtensions.Join is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Join instead.")] + public static string Join(this IEnumerable enumeration, Func converter = null, string delimiter = ", ") => HarmonyLib.GeneralExtensions.Join(enumeration, converter, delimiter); + [Obsolete("Harmony.GeneralExtensions.Description is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Description instead.")] + public static string Description(this Type[] parameters) => HarmonyLib.GeneralExtensions.Description(parameters); + [Obsolete("Harmony.GeneralExtensions.FullDescription is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.FullDescription instead.")] + public static string FullDescription(this MethodBase method) => HarmonyLib.GeneralExtensions.FullDescription(method); + [Obsolete("Harmony.GeneralExtensions.Types is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Types instead.")] + public static Type[] Types(this ParameterInfo[] pinfo) => HarmonyLib.GeneralExtensions.Types(pinfo); + [Obsolete("Harmony.GeneralExtensions.GetValueSafe is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetValueSafe instead.")] + public static T GetValueSafe(this Dictionary dictionary, S key) => HarmonyLib.GeneralExtensions.GetValueSafe(dictionary, key); + [Obsolete("Harmony.GeneralExtensions.GetTypedValue is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetTypedValue instead.")] + public static T GetTypedValue(this Dictionary dictionary, string key) => HarmonyLib.GeneralExtensions.GetTypedValue(dictionary, key); +} - [Obsolete("Harmony.CollectionExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions instead.")] - public static class CollectionExtensions - { - [Obsolete("Harmony.CollectionExtensions.Do is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Do instead.")] - public static void Do(this IEnumerable sequence, Action action) => HarmonyLib.CollectionExtensions.Do(sequence, action); - [Obsolete("Harmony.CollectionExtensions.DoIf is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.DoIf instead.")] - public static void DoIf(this IEnumerable sequence, Func condition, Action action) => HarmonyLib.CollectionExtensions.DoIf(sequence, condition, action); - [Obsolete("Harmony.CollectionExtensions.Add is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Add instead.")] - public static IEnumerable Add(this IEnumerable sequence, T item) => HarmonyLib.CollectionExtensions.AddItem(sequence, item); - [Obsolete("Harmony.CollectionExtensions.AddRangeToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddRangeToArray instead.")] - public static T[] AddRangeToArray(this T[] sequence, T[] items) => HarmonyLib.CollectionExtensions.AddRangeToArray(sequence, items); - [Obsolete("Harmony.CollectionExtensions.AddToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddToArray instead.")] - public static T[] AddToArray(this T[] sequence, T item) => HarmonyLib.CollectionExtensions.AddToArray(sequence, item); - } +[Obsolete("Harmony.CollectionExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions instead.")] +public static class CollectionExtensions +{ + [Obsolete("Harmony.CollectionExtensions.Do is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Do instead.")] + public static void Do(this IEnumerable sequence, Action action) => HarmonyLib.CollectionExtensions.Do(sequence, action); + [Obsolete("Harmony.CollectionExtensions.DoIf is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.DoIf instead.")] + public static void DoIf(this IEnumerable sequence, Func condition, Action action) => HarmonyLib.CollectionExtensions.DoIf(sequence, condition, action); + [Obsolete("Harmony.CollectionExtensions.Add is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Add instead.")] + public static IEnumerable Add(this IEnumerable sequence, T item) => HarmonyLib.CollectionExtensions.AddItem(sequence, item); + [Obsolete("Harmony.CollectionExtensions.AddRangeToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddRangeToArray instead.")] + public static T[] AddRangeToArray(this T[] sequence, T[] items) => HarmonyLib.CollectionExtensions.AddRangeToArray(sequence, items); + [Obsolete("Harmony.CollectionExtensions.AddToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddToArray instead.")] + public static T[] AddToArray(this T[] sequence, T item) => HarmonyLib.CollectionExtensions.AddToArray(sequence, item); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs b/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs index a5bbc5cdd..7597f8504 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs @@ -2,18 +2,17 @@ using System.Linq.Expressions; using System.Reflection; -namespace Harmony +namespace Harmony; + +[Obsolete("Harmony.SymbolExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions instead.")] +public static class SymbolExtensions { - [Obsolete("Harmony.SymbolExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions instead.")] - public static class SymbolExtensions - { - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] - public static MethodInfo GetMethodInfo(Expression expression) => HarmonyLib.SymbolExtensions.GetMethodInfo(expression); - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] - public static MethodInfo GetMethodInfo(Expression> expression) => GetMethodInfo((LambdaExpression)expression); - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] - public static MethodInfo GetMethodInfo(Expression> expression) => GetMethodInfo((LambdaExpression)expression); - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] - public static MethodInfo GetMethodInfo(LambdaExpression expression) => HarmonyLib.SymbolExtensions.GetMethodInfo(expression); - } + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + public static MethodInfo GetMethodInfo(Expression expression) => HarmonyLib.SymbolExtensions.GetMethodInfo(expression); + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + public static MethodInfo GetMethodInfo(Expression> expression) => GetMethodInfo((LambdaExpression)expression); + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + public static MethodInfo GetMethodInfo(Expression> expression) => GetMethodInfo((LambdaExpression)expression); + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + public static MethodInfo GetMethodInfo(LambdaExpression expression) => HarmonyLib.SymbolExtensions.GetMethodInfo(expression); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs b/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs index f4ab132f5..610dcb458 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs @@ -1,7 +1,6 @@ using System; -namespace MelonLoader.MonoInternals -{ - [Obsolete("MelonLoader.MonoInternals.AssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.AssemblyResolveInfo instead.")] - public class AssemblyResolveInfo : Resolver.AssemblyResolveInfo { } -} +namespace MelonLoader.MonoInternals; + +[Obsolete("MelonLoader.MonoInternals.AssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.AssemblyResolveInfo instead.")] +public class AssemblyResolveInfo : Resolver.AssemblyResolveInfo { } diff --git a/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs b/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs index 5a107f60e..4a8986b9a 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs @@ -1,8 +1,7 @@ using System; -namespace Harmony -{ - [Obsolete("Harmony.HarmonyShield is Only Here for Compatibility Reasons. Please use MelonLoader.PatchShield instead.")] - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)] - public class HarmonyShield : MelonLoader.PatchShield { } -} +namespace Harmony; + +[Obsolete("Harmony.HarmonyShield is Only Here for Compatibility Reasons. Please use MelonLoader.PatchShield instead.")] +[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)] +public class HarmonyShield : MelonLoader.PatchShield { } diff --git a/MelonLoader/BackwardsCompatibility/Melon/Imports.cs b/MelonLoader/BackwardsCompatibility/Melon/Imports.cs index 731c312a4..2fd7624f7 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/Imports.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/Imports.cs @@ -1,28 +1,27 @@ -using System; -using MelonLoader.Utils; +using MelonLoader.Utils; +using System; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.Imports is Only Here for Compatibility Reasons.")] +public static class Imports { - [Obsolete("MelonLoader.Imports is Only Here for Compatibility Reasons.")] - public static class Imports - { - [Obsolete("MelonLoader.Imports.GetCompanyName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead.")] - public static string GetCompanyName() => InternalUtils.UnityInformationHandler.GameDeveloper; - [Obsolete("MelonLoader.Imports.GetProductName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead.")] - public static string GetProductName() => InternalUtils.UnityInformationHandler.GameName; - [Obsolete("MelonLoader.Imports.GetGameDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.GameRootDirectory instead.")] - public static string GetGameDirectory() => MelonEnvironment.GameRootDirectory; - [Obsolete("MelonLoader.Imports.GetGameDataDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UnityGameDataDirectory instead.")] - public static string GetGameDataDirectory() => MelonEnvironment.UnityGameDataDirectory; - [Obsolete("MelonLoader.Imports.GetAssemblyDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.MelonManagedDirectory instead.")] - public static string GetAssemblyDirectory() => MelonEnvironment.MelonManagedDirectory; - [Obsolete("MelonLoader.Imports.IsIl2CppGame is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsGameIl2Cpp instead.")] - public static bool IsIl2CppGame() => MelonUtils.IsGameIl2Cpp(); - [Obsolete("MelonLoader.Imports.IsDebugMode is Only Here for Compatibility Reasons. Please use MelonLoader.MelonDebug.IsEnabled instead.")] - public static bool IsDebugMode() => MelonDebug.IsEnabled(); - [Obsolete("MelonLoader.Imports.Hook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookAttach instead.")] - public static void Hook(IntPtr target, IntPtr detour) => MelonUtils.NativeHookAttach(target, detour); - [Obsolete("MelonLoader.Imports.Unhook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookDetach instead.")] - public static void Unhook(IntPtr target, IntPtr detour) => MelonUtils.NativeHookDetach(target, detour); - } + [Obsolete("MelonLoader.Imports.GetCompanyName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead.")] + public static string GetCompanyName() => InternalUtils.UnityInformationHandler.GameDeveloper; + [Obsolete("MelonLoader.Imports.GetProductName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead.")] + public static string GetProductName() => InternalUtils.UnityInformationHandler.GameName; + [Obsolete("MelonLoader.Imports.GetGameDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.GameRootDirectory instead.")] + public static string GetGameDirectory() => MelonEnvironment.GameRootDirectory; + [Obsolete("MelonLoader.Imports.GetGameDataDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UnityGameDataDirectory instead.")] + public static string GetGameDataDirectory() => MelonEnvironment.UnityGameDataDirectory; + [Obsolete("MelonLoader.Imports.GetAssemblyDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.MelonManagedDirectory instead.")] + public static string GetAssemblyDirectory() => MelonEnvironment.MelonManagedDirectory; + [Obsolete("MelonLoader.Imports.IsIl2CppGame is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsGameIl2Cpp instead.")] + public static bool IsIl2CppGame() => MelonUtils.IsGameIl2Cpp(); + [Obsolete("MelonLoader.Imports.IsDebugMode is Only Here for Compatibility Reasons. Please use MelonLoader.MelonDebug.IsEnabled instead.")] + public static bool IsDebugMode() => MelonDebug.IsEnabled(); + [Obsolete("MelonLoader.Imports.Hook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookAttach instead.")] + public static void Hook(IntPtr target, IntPtr detour) => MelonUtils.NativeHookAttach(target, detour); + [Obsolete("MelonLoader.Imports.Unhook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookDetach instead.")] + public static void Unhook(IntPtr target, IntPtr detour) => MelonUtils.NativeHookDetach(target, detour); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/Main.cs b/MelonLoader/BackwardsCompatibility/Melon/Main.cs index d6c551905..bdca93ab0 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/Main.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/Main.cs @@ -1,21 +1,20 @@ -using System; +using MelonLoader.Utils; +using System; using System.Collections.Generic; -using MelonLoader.Utils; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.Main is Only Here for Compatibility Reasons.")] +public static class Main { - [Obsolete("MelonLoader.Main is Only Here for Compatibility Reasons.")] - public static class Main - { - [Obsolete("MelonLoader.Main.Mods is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Mods instead.")] - public static List Mods = null; - [Obsolete("MelonLoader.Main.Plugins is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Plugins instead.")] - public static List Plugins = null; - [Obsolete("MelonLoader.Main.IsBoneworks is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsBONEWORKS instead.")] - public static bool IsBoneworks = false; - [Obsolete("MelonLoader.Main.GetUnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] - public static string GetUnityVersion() => InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); - [Obsolete("MelonLoader.Main.GetUserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead.")] - public static string GetUserDataPath() => MelonEnvironment.UserDataDirectory; - } + [Obsolete("MelonLoader.Main.Mods is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Mods instead.")] + public static List Mods = null; + [Obsolete("MelonLoader.Main.Plugins is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Plugins instead.")] + public static List Plugins = null; + [Obsolete("MelonLoader.Main.IsBoneworks is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsBONEWORKS instead.")] + public static bool IsBoneworks = false; + [Obsolete("MelonLoader.Main.GetUnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] + public static string GetUnityVersion() => InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); + [Obsolete("MelonLoader.Main.GetUserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead.")] + public static string GetUserDataPath() => MelonEnvironment.UserDataDirectory; } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs index 4f3889dd5..fae25b5f7 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs @@ -1,11 +1,10 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonConsole is Only Here for Compatibility Reasons.")] +public class MelonConsole { - [Obsolete("MelonLoader.MelonConsole is Only Here for Compatibility Reasons.")] - public class MelonConsole - { - [Obsolete("MelonLoader.MelonConsole.SetTitle is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.SetConsoleTitle instead.")] - public static void SetTitle(string title) => MelonUtils.SetConsoleTitle(title); - } + [Obsolete("MelonLoader.MelonConsole.SetTitle is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.SetConsoleTitle instead.")] + public static void SetTitle(string title) => MelonUtils.SetConsoleTitle(title); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs index 10ef79036..e446aea96 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs @@ -1,14 +1,13 @@ -using System; -using MelonLoader.Utils; +using MelonLoader.Utils; +using System; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonLoaderBase is Only Here for Compatibility Reasons.")] +public static class MelonLoaderBase { - [Obsolete("MelonLoader.MelonLoaderBase is Only Here for Compatibility Reasons.")] - public static class MelonLoaderBase - { - [Obsolete("MelonLoader.MelonLoaderBase.UserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead.")] - public static string UserDataPath { get => MelonEnvironment.UserDataDirectory; } - [Obsolete("MelonLoader.MelonLoaderBase.UnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] - public static string UnityVersion { get => InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); } - } + [Obsolete("MelonLoader.MelonLoaderBase.UserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead.")] + public static string UserDataPath { get => MelonEnvironment.UserDataDirectory; } + [Obsolete("MelonLoader.MelonLoaderBase.UnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] + public static string UnityVersion { get => InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs index 9afb8776d..b012a47f8 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs @@ -1,16 +1,15 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonModGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class MelonModGameAttribute : MelonGameAttribute { + [Obsolete("MelonLoader.MelonModGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead.")] + public new string Developer => base.Developer; + [Obsolete("MelonLoader.MelonModGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead.")] + public string GameName => Name; [Obsolete("MelonLoader.MelonModGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class MelonModGameAttribute : MelonGameAttribute - { - [Obsolete("MelonLoader.MelonModGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead.")] - new public string Developer => base.Developer; - [Obsolete("MelonLoader.MelonModGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead.")] - public string GameName => Name; - [Obsolete("MelonLoader.MelonModGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] - public MelonModGameAttribute(string developer = null, string gameName = null) : base(developer, gameName) { } - } + public MelonModGameAttribute(string developer = null, string gameName = null) : base(developer, gameName) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs index 25099286d..8287f0e52 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs @@ -1,22 +1,21 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] +public class MelonModInfoAttribute : MelonInfoAttribute { + [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead.")] + public new Type SystemType => base.SystemType; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead.")] + public new string Name => base.Name; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead.")] + public new string Version => base.Version; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead.")] + public new string Author => base.Author; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead.")] + public new string DownloadLink => base.DownloadLink; [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] - public class MelonModInfoAttribute : MelonInfoAttribute - { - [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead.")] - new public Type SystemType => base.SystemType; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead.")] - new public string Name => base.Name; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead.")] - new public string Version => base.Version; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead.")] - new public string Author => base.Author; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead.")] - new public string DownloadLink => base.DownloadLink; - [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] - public MelonModInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) : base(type, name, version, author, downloadLink) { } - } + public MelonModInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) : base(type, name, version, author, downloadLink) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs index 80e40c13b..ddf59d1f6 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs @@ -1,7 +1,6 @@ using System; -namespace MelonLoader -{ - [Obsolete("MelonLoader.MelonModLogger is Only Here for Compatibility Reasons. Please use MelonLoader.MelonLogger instead.")] - public class MelonModLogger : MelonLogger { } -} \ No newline at end of file +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonModLogger is Only Here for Compatibility Reasons. Please use MelonLoader.MelonLogger instead.")] +public class MelonModLogger : MelonLogger { } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs index 9bd82d6a8..5e4658fad 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs @@ -1,16 +1,15 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonPluginGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class MelonPluginGameAttribute : MelonGameAttribute { + [Obsolete("MelonLoader.MelonPluginGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead.")] + public new string Developer => base.Developer; + [Obsolete("MelonLoader.MelonPluginGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead.")] + public string GameName => Name; [Obsolete("MelonLoader.MelonPluginGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class MelonPluginGameAttribute : MelonGameAttribute - { - [Obsolete("MelonLoader.MelonPluginGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead.")] - new public string Developer => base.Developer; - [Obsolete("MelonLoader.MelonPluginGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead.")] - public string GameName => Name; - [Obsolete("MelonLoader.MelonPluginGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] - public MelonPluginGameAttribute(string developer = null, string gameName = null) : base(developer, gameName) { } - } + public MelonPluginGameAttribute(string developer = null, string gameName = null) : base(developer, gameName) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs index 717e7dc1c..e84c4e5ff 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs @@ -1,22 +1,21 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] +public class MelonPluginInfoAttribute : MelonInfoAttribute { + [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead.")] + public new Type SystemType => base.SystemType; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead.")] + public new string Name => base.Name; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead.")] + public new string Version => base.Version; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead.")] + public new string Author => base.Author; + [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead.")] + public new string DownloadLink => base.DownloadLink; [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] - public class MelonPluginInfoAttribute : MelonInfoAttribute - { - [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead.")] - new public Type SystemType => base.SystemType; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead.")] - new public string Name => base.Name; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead.")] - new public string Version => base.Version; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead.")] - new public string Author => base.Author; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead.")] - new public string DownloadLink => base.DownloadLink; - [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] - public MelonPluginInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) : base(type, name, version, author, downloadLink) { } - } + public MelonPluginInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) : base(type, name, version, author, downloadLink) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs index 804ca31dd..d40c21131 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs @@ -1,184 +1,178 @@ using System; using System.Collections.Generic; -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.MelonPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] +public class MelonPrefs { - [Obsolete("MelonLoader.MelonPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] - public class MelonPrefs + [Obsolete("MelonLoader.MelonPrefs.RegisterCategory is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateCategory instead.")] + public static void RegisterCategory(string name, string displayText) => MelonPreferences.CreateCategory(name, displayText); + [Obsolete("MelonLoader.MelonPrefs.RegisterString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterString(string section, string name, string defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.MelonPrefs.RegisterBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterBool(string section, string name, bool defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.MelonPrefs.RegisterInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterInt(string section, string name, int defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.MelonPrefs.RegisterFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterFloat(string section, string name, float defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.MelonPrefs.HasKey is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.HasEntry instead.")] + public static bool HasKey(string section, string name) => MelonPreferences.HasEntry(section, name); + [Obsolete("MelonLoader.MelonPrefs.GetPreferences is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Categories instead.")] + public static Dictionary> GetPreferences() { - [Obsolete("MelonLoader.MelonPrefs.RegisterCategory is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateCategory instead.")] - public static void RegisterCategory(string name, string displayText) => MelonPreferences.CreateCategory(name, displayText); - [Obsolete("MelonLoader.MelonPrefs.RegisterString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterString(string section, string name, string defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.RegisterBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterBool(string section, string name, bool defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.RegisterInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterInt(string section, string name, int defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.RegisterFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterFloat(string section, string name, float defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.HasKey is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.HasEntry instead.")] - public static bool HasKey(string section, string name) => MelonPreferences.HasEntry(section, name); - [Obsolete("MelonLoader.MelonPrefs.GetPreferences is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Categories instead.")] - public static Dictionary> GetPreferences() + Dictionary> output = []; + if (MelonPreferences.Categories.Count <= 0) + return output; + foreach (var category in MelonPreferences.Categories) { - Dictionary> output = new Dictionary>(); - if (MelonPreferences.Categories.Count <= 0) - return output; - foreach (MelonPreferences_Category category in MelonPreferences.Categories) + Dictionary newprefsdict = []; + foreach (var entry in category.Entries) { - Dictionary newprefsdict = new Dictionary(); - foreach (MelonPreferences_Entry entry in category.Entries) - { - Type reflectedType = entry.GetReflectedType(); - if ((reflectedType != typeof(string)) - && (reflectedType != typeof(bool)) - && (reflectedType != typeof(int)) - && (reflectedType != typeof(float)) - && (reflectedType != typeof(double)) - && (reflectedType != typeof(long))) - continue; - MelonPreference newpref = new MelonPreference(entry); - newprefsdict.Add(entry.Identifier, newpref); - } - output.Add(category.Identifier, newprefsdict); + var reflectedType = entry.GetReflectedType(); + if ((reflectedType != typeof(string)) + && (reflectedType != typeof(bool)) + && (reflectedType != typeof(int)) + && (reflectedType != typeof(float)) + && (reflectedType != typeof(double)) + && (reflectedType != typeof(long))) + continue; + var newpref = new MelonPreference(entry); + newprefsdict.Add(entry.Identifier, newpref); + } + output.Add(category.Identifier, newprefsdict); + } + return output; + } + [Obsolete("MelonLoader.MelonPrefs.GetCategoryDisplayName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetCategoryDisplayName instead.")] + public static string GetCategoryDisplayName(string key) => MelonPreferences.GetCategory(key)?.DisplayName; + [Obsolete("MelonLoader.MelonPrefs.SaveConfig is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Save instead.")] + public static void SaveConfig() => MelonPreferences.Save(); + [Obsolete("MelonLoader.MelonPrefs.GetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryString instead.")] + public static string GetString(string section, string name) + { + var category = MelonPreferences.GetCategory(section); + if (category == null) + return null; + var entry = category.GetEntry(name); + return entry == null ? null : entry.GetValueAsString(); + } + [Obsolete("MelonLoader.MelonPrefs.SetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryString instead.")] + public static void SetString(string section, string name, string value) + { + var category = MelonPreferences.GetCategory(section); + if (category == null) + return; + var entry = category.GetEntry(name); + if (entry == null) + return; + switch (entry) + { + case MelonPreferences_Entry stringEntry: + stringEntry.Value = value; + break; + case MelonPreferences_Entry intEntry: + if (int.TryParse(value, out var parsedInt)) + intEntry.Value = parsedInt; + break; + case MelonPreferences_Entry floatEntry: + if (float.TryParse(value, out var parsedFloat)) + floatEntry.Value = parsedFloat; + break; + case MelonPreferences_Entry boolEntry: + if (value.ToLower().StartsWith("true") || value.ToLower().StartsWith("false")) + boolEntry.Value = value.ToLower().StartsWith("true"); + break; + } + } + [Obsolete("MelonLoader.MelonPrefs.GetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryBool instead.")] + public static bool GetBool(string section, string name) => MelonPreferences.GetEntryValue(section, name); + [Obsolete("MelonLoader.MelonPrefs.SetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryBool instead.")] + public static void SetBool(string section, string name, bool value) => MelonPreferences.SetEntryValue(section, name, value); + [Obsolete("MelonLoader.MelonPrefs.GetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryInt instead.")] + public static int GetInt(string section, string name) => MelonPreferences.GetEntryValue(section, name); + [Obsolete("MelonLoader.MelonPrefs.SetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryInt instead.")] + public static void SetInt(string section, string name, int value) => MelonPreferences.SetEntryValue(section, name, value); + [Obsolete("MelonLoader.MelonPrefs.GetFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryFloat instead.")] + public static float GetFloat(string section, string name) => MelonPreferences.GetEntryValue(section, name); + [Obsolete("MelonLoader.MelonPrefs.GetEntryFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryFloat instead.")] + public static void SetFloat(string section, string name, float value) => MelonPreferences.SetEntryValue(section, name, value); + [Obsolete("MelonLoader.MelonPrefs.MelonPreferenceType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead.")] + public enum MelonPreferenceType + { + STRING, + BOOL, + INT, + FLOAT + } + [Obsolete("MelonLoader.MelonPrefs.MelonPreference is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] + public class MelonPreference + { + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Value is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValue instead.")] + public string Value { get => GetString(Entry.Category.Identifier, Entry.Identifier); set => SetString(Entry.Category.Identifier, Entry.Identifier, value); } + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.ValueEdited is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValueEdited instead.")] + public string ValueEdited { get => GetEditedString(Entry.Category.Identifier, Entry.Identifier); set => SetEditedString(Entry.Category.Identifier, Entry.Identifier, value); } + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetReflectedType instead.")] + public MelonPreferenceType Type + { + get + { + if (Entry.GetReflectedType() == typeof(string)) + return MelonPreferenceType.STRING; + else if (Entry.GetReflectedType() == typeof(bool)) + return MelonPreferenceType.BOOL; + else if ((Entry.GetReflectedType() == typeof(float)) + || (Entry.GetReflectedType() == typeof(double))) + return MelonPreferenceType.FLOAT; + else if ((Entry.GetReflectedType() == typeof(int)) + || (Entry.GetReflectedType() == typeof(long)) + || (Entry.GetReflectedType() == typeof(byte))) + return MelonPreferenceType.INT; + return (MelonPreferenceType)4; } - return output; } - [Obsolete("MelonLoader.MelonPrefs.GetCategoryDisplayName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetCategoryDisplayName instead.")] - public static string GetCategoryDisplayName(string key) => MelonPreferences.GetCategory(key)?.DisplayName; - [Obsolete("MelonLoader.MelonPrefs.SaveConfig is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Save instead.")] - public static void SaveConfig() => MelonPreferences.Save(); - [Obsolete("MelonLoader.MelonPrefs.GetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryString instead.")] - public static string GetString(string section, string name) + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Hidden is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.IsHidden instead.")] + public bool Hidden { get => Entry.IsHidden; } + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.DisplayText is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.DisplayName instead.")] + public string DisplayText { get => Entry.DisplayName; } + + internal MelonPreference(MelonPreferences_Entry entry) => Entry = entry; + internal MelonPreference(MelonPreference pref) => Entry = pref.Entry; + private readonly MelonPreferences_Entry Entry = null; + private static string GetEditedString(string section, string name) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); + var category = MelonPreferences.GetCategory(section); if (category == null) return null; - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - return null; - return entry.GetValueAsString(); + var entry = category.GetEntry(name); + return entry == null ? null : entry.GetEditedValueAsString(); } - [Obsolete("MelonLoader.MelonPrefs.SetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryString instead.")] - public static void SetString(string section, string name, string value) + private static void SetEditedString(string section, string name, string value) { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); + var category = MelonPreferences.GetCategory(section); if (category == null) return; - MelonPreferences_Entry entry = category.GetEntry(name); + var entry = category.GetEntry(name); if (entry == null) return; switch (entry) { case MelonPreferences_Entry stringEntry: - stringEntry.Value = value; + stringEntry.EditedValue = value; break; case MelonPreferences_Entry intEntry: if (int.TryParse(value, out var parsedInt)) - intEntry.Value = parsedInt; + intEntry.EditedValue = parsedInt; break; case MelonPreferences_Entry floatEntry: if (float.TryParse(value, out var parsedFloat)) - floatEntry.Value = parsedFloat; + floatEntry.EditedValue = parsedFloat; break; case MelonPreferences_Entry boolEntry: if (value.ToLower().StartsWith("true") || value.ToLower().StartsWith("false")) - boolEntry.Value = value.ToLower().StartsWith("true"); + boolEntry.EditedValue = value.ToLower().StartsWith("true"); break; } } - [Obsolete("MelonLoader.MelonPrefs.GetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryBool instead.")] - public static bool GetBool(string section, string name) => MelonPreferences.GetEntryValue(section, name); - [Obsolete("MelonLoader.MelonPrefs.SetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryBool instead.")] - public static void SetBool(string section, string name, bool value) => MelonPreferences.SetEntryValue(section, name, value); - [Obsolete("MelonLoader.MelonPrefs.GetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryInt instead.")] - public static int GetInt(string section, string name) => MelonPreferences.GetEntryValue(section, name); - [Obsolete("MelonLoader.MelonPrefs.SetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryInt instead.")] - public static void SetInt(string section, string name, int value) => MelonPreferences.SetEntryValue(section, name, value); - [Obsolete("MelonLoader.MelonPrefs.GetFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryFloat instead.")] - public static float GetFloat(string section, string name) => MelonPreferences.GetEntryValue(section, name); - [Obsolete("MelonLoader.MelonPrefs.GetEntryFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryFloat instead.")] - public static void SetFloat(string section, string name, float value) => MelonPreferences.SetEntryValue(section, name, value); - [Obsolete("MelonLoader.MelonPrefs.MelonPreferenceType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead.")] - public enum MelonPreferenceType - { - STRING, - BOOL, - INT, - FLOAT - } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] - public class MelonPreference - { - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Value is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValue instead.")] - public string Value { get => GetString(Entry.Category.Identifier, Entry.Identifier); set => SetString(Entry.Category.Identifier, Entry.Identifier, value); } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.ValueEdited is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValueEdited instead.")] - public string ValueEdited { get => GetEditedString(Entry.Category.Identifier, Entry.Identifier); set => SetEditedString(Entry.Category.Identifier, Entry.Identifier, value); } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetReflectedType instead.")] - public MelonPreferenceType Type - { - get - { - if (Entry.GetReflectedType() == typeof(string)) - return MelonPreferenceType.STRING; - else if (Entry.GetReflectedType() == typeof(bool)) - return MelonPreferenceType.BOOL; - else if ((Entry.GetReflectedType() == typeof(float)) - || (Entry.GetReflectedType() == typeof(double))) - return MelonPreferenceType.FLOAT; - else if ((Entry.GetReflectedType() == typeof(int)) - || (Entry.GetReflectedType() == typeof(long)) - || (Entry.GetReflectedType() == typeof(byte))) - return MelonPreferenceType.INT; - return (MelonPreferenceType)4; - } - } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Hidden is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.IsHidden instead.")] - public bool Hidden { get => Entry.IsHidden; } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.DisplayText is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.DisplayName instead.")] - public string DisplayText { get => Entry.DisplayName; } - - internal MelonPreference(MelonPreferences_Entry entry) => Entry = entry; - internal MelonPreference(MelonPreference pref) => Entry = pref.Entry; - private MelonPreferences_Entry Entry = null; - private static string GetEditedString(string section, string name) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - return null; - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - return null; - - return entry.GetEditedValueAsString(); - } - private static void SetEditedString(string section, string name, string value) - { - MelonPreferences_Category category = MelonPreferences.GetCategory(section); - if (category == null) - return; - MelonPreferences_Entry entry = category.GetEntry(name); - if (entry == null) - return; - switch (entry) - { - case MelonPreferences_Entry stringEntry: - stringEntry.EditedValue = value; - break; - case MelonPreferences_Entry intEntry: - if (int.TryParse(value, out var parsedInt)) - intEntry.EditedValue = parsedInt; - break; - case MelonPreferences_Entry floatEntry: - if (float.TryParse(value, out var parsedFloat)) - floatEntry.EditedValue = parsedFloat; - break; - case MelonPreferences_Entry boolEntry: - if (value.ToLower().StartsWith("true") || value.ToLower().StartsWith("false")) - boolEntry.EditedValue = value.ToLower().StartsWith("true"); - break; - } - } - } } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs b/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs index 70f7a3152..1d1f4ccc9 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs @@ -3,56 +3,57 @@ using System.Linq; #pragma warning disable 0108 -namespace MelonLoader +namespace MelonLoader; + +[Obsolete("MelonLoader.ModPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] +public class ModPrefs : MelonPrefs { - [Obsolete("MelonLoader.ModPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] - public class ModPrefs : MelonPrefs + [Obsolete("MelonLoader.ModPrefs.GetPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] + public static Dictionary> GetPrefs() { - [Obsolete("MelonLoader.ModPrefs.GetPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] - public static Dictionary> GetPrefs() + Dictionary> output = []; + var prefs = GetPreferences(); + for (var i = 0; i < prefs.Values.Count; i++) { - Dictionary> output = new Dictionary>(); - Dictionary> prefs = GetPreferences(); - for (int i = 0; i < prefs.Values.Count; i++) + var prefsdict = prefs.Values.ElementAt(i); + Dictionary newprefsdict = []; + for (var j = 0; j < prefsdict.Values.Count; j++) { - Dictionary prefsdict = prefs.Values.ElementAt(i); - Dictionary newprefsdict = new Dictionary(); - for (int j = 0; j < prefsdict.Values.Count; j++) + var pref = prefsdict.Values.ElementAt(j); + var newpref = new PrefDesc(pref) { - MelonPreference pref = prefsdict.Values.ElementAt(j); - PrefDesc newpref = new PrefDesc(pref); - newpref.ValueEdited = pref.ValueEdited; - newprefsdict.Add(prefsdict.Keys.ElementAt(j), newpref); - } - output.Add(prefs.Keys.ElementAt(i), newprefsdict); + ValueEdited = pref.ValueEdited + }; + newprefsdict.Add(prefsdict.Keys.ElementAt(j), newpref); } - return output; - } - [Obsolete("MelonLoader.ModPrefs.RegisterPrefString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterPrefString(string section, string name, string defaultValue, string displayText = null, bool hideFromList = false) => RegisterString(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.RegisterPrefBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterPrefBool(string section, string name, bool defaultValue, string displayText = null, bool hideFromList = false) => RegisterBool(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.RegisterPrefInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterPrefInt(string section, string name, int defaultValue, string displayText = null, bool hideFromList = false) => RegisterInt(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.RegisterPrefFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] - public static void RegisterPrefFloat(string section, string name, float defaultValue, string displayText = null, bool hideFromList = false) => RegisterFloat(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.PrefType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead.")] - public enum PrefType - { - STRING, - BOOL, - INT, - FLOAT + output.Add(prefs.Keys.ElementAt(i), newprefsdict); } + return output; + } + [Obsolete("MelonLoader.ModPrefs.RegisterPrefString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterPrefString(string section, string name, string defaultValue, string displayText = null, bool hideFromList = false) => RegisterString(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.ModPrefs.RegisterPrefBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterPrefBool(string section, string name, bool defaultValue, string displayText = null, bool hideFromList = false) => RegisterBool(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.ModPrefs.RegisterPrefInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterPrefInt(string section, string name, int defaultValue, string displayText = null, bool hideFromList = false) => RegisterInt(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.ModPrefs.RegisterPrefFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + public static void RegisterPrefFloat(string section, string name, float defaultValue, string displayText = null, bool hideFromList = false) => RegisterFloat(section, name, defaultValue, displayText, hideFromList); + [Obsolete("MelonLoader.ModPrefs.PrefType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead.")] + public enum PrefType + { + STRING, + BOOL, + INT, + FLOAT + } + [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] + public class PrefDesc : MelonPreference + { + [Obsolete("MelonLoader.ModPrefs.PrefDesc.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.Type instead.")] + public PrefType Type { get => (PrefType)base.Type; } [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] - public class PrefDesc : MelonPreference - { - [Obsolete("MelonLoader.ModPrefs.PrefDesc.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.Type instead.")] - public PrefType Type { get => (PrefType)base.Type; } - [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] - public PrefDesc(MelonPreferences_Entry entry) : base(entry) { } - [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] - public PrefDesc(MelonPreference pref) : base(pref) { } - } + public PrefDesc(MelonPreferences_Entry entry) : base(entry) { } + [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] + public PrefDesc(MelonPreference pref) : base(pref) { } } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs b/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs index e6cc27192..405e79552 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs @@ -2,10 +2,9 @@ using System; -namespace MelonLoader.MonoInternals -{ - [Obsolete("MelonLoader.MonoInternals.MonoLibrary is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MonoLibrary instead.")] - public class MonoLibrary : Utils.MonoLibrary { } -} +namespace MelonLoader.MonoInternals; + +[Obsolete("MelonLoader.MonoInternals.MonoLibrary is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MonoLibrary instead.")] +public class MonoLibrary : Utils.MonoLibrary { } #endif \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs b/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs index 03c0ba138..e13a662ee 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs @@ -1,39 +1,38 @@ using System; using System.Reflection; -namespace MelonLoader.MonoInternals +namespace MelonLoader.MonoInternals; + +[Obsolete("MelonLoader.MonoInternals.MonoResolveManager is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver instead.")] +public static class MonoResolveManager { - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver instead.")] - public static class MonoResolveManager - { - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.AddSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.AddSearchDirectory instead.")] - public static void AddSearchDirectory(string path, int priority = 0) - => Resolver.SearchDirectoryManager.Add(path, priority); + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.AddSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.AddSearchDirectory instead.")] + public static void AddSearchDirectory(string path, int priority = 0) + => Resolver.SearchDirectoryManager.Add(path, priority); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.RemoveSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.RemoveSearchDirectory instead.")] - public static void RemoveSearchDirectory(string path) - => Resolver.SearchDirectoryManager.Remove(path); + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.RemoveSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.RemoveSearchDirectory instead.")] + public static void RemoveSearchDirectory(string path) + => Resolver.SearchDirectoryManager.Remove(path); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoadHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyLoad instead.")] - public delegate void OnAssemblyLoadHandler(Assembly assembly); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead.")] - public static event OnAssemblyLoadHandler OnAssemblyLoad; - internal static void SafeInvoke_OnAssemblyLoad(Assembly assembly) - => OnAssemblyLoad?.Invoke(assembly); + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoadHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyLoad instead.")] + public delegate void OnAssemblyLoadHandler(Assembly assembly); + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead.")] + public static event OnAssemblyLoadHandler OnAssemblyLoad; + internal static void SafeInvoke_OnAssemblyLoad(Assembly assembly) + => OnAssemblyLoad?.Invoke(assembly); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyResolveHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyResolve instead.")] - public delegate Assembly OnAssemblyResolveHandler(string name, Version version); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead.")] - public static event OnAssemblyResolveHandler OnAssemblyResolve; - internal static Assembly SafeInvoke_OnAssemblyResolve(string name, Version version) - => OnAssemblyResolve?.Invoke(name, version); + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyResolveHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyResolve instead.")] + public delegate Assembly OnAssemblyResolveHandler(string name, Version version); + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead.")] + public static event OnAssemblyResolveHandler OnAssemblyResolve; + internal static Assembly SafeInvoke_OnAssemblyResolve(string name, Version version) + => OnAssemblyResolve?.Invoke(name, version); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.GetAssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.GetAssemblyResolveInfo instead.")] - public static AssemblyResolveInfo GetAssemblyResolveInfo(string name) - => (AssemblyResolveInfo)Resolver.AssemblyManager.GetInfo(name); + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.GetAssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.GetAssemblyResolveInfo instead.")] + public static AssemblyResolveInfo GetAssemblyResolveInfo(string name) + => (AssemblyResolveInfo)Resolver.AssemblyManager.GetInfo(name); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.LoadInfoFromAssembly is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.LoadInfoFromAssembly instead.")] - public static void LoadInfoFromAssembly(Assembly assembly) - => Resolver.AssemblyManager.LoadInfo(assembly); - } + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.LoadInfoFromAssembly is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.LoadInfoFromAssembly instead.")] + public static void LoadInfoFromAssembly(Assembly assembly) + => Resolver.AssemblyManager.LoadInfo(assembly); } diff --git a/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs b/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs index d5b878f94..f29ba9dda 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs @@ -3,256 +3,256 @@ using System.Runtime.InteropServices; #pragma warning disable 0618 -namespace MelonLoader +namespace MelonLoader; + +public static class bHaptics { - public static class bHaptics - { - public static bool WasError { get => false; } + public static bool WasError { get => false; } - [Obsolete("MelonLoader.bHaptics.IsPlaying is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlayingAny instead.")] - public static bool IsPlaying() - => bHapticsLib.bHapticsManager.IsPlayingAny(); - [Obsolete("MelonLoader.bHaptics.IsPlaying(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlaying instead.")] - public static bool IsPlaying(string key) - => bHapticsLib.bHapticsManager.IsPlaying(key); + [Obsolete("MelonLoader.bHaptics.IsPlaying is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlayingAny instead.")] + public static bool IsPlaying() + => bHapticsLib.bHapticsManager.IsPlayingAny(); + [Obsolete("MelonLoader.bHaptics.IsPlaying(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlaying instead.")] + public static bool IsPlaying(string key) + => bHapticsLib.bHapticsManager.IsPlaying(key); - [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(DeviceType, bool) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead.")] - public static bool IsDeviceConnected(DeviceType type, bool isLeft = true) => IsDeviceConnected(DeviceTypeToPositionType(type, isLeft)); - [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(PositionType) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead.")] - public static bool IsDeviceConnected(PositionType type) - => bHapticsLib.bHapticsManager.IsDeviceConnected(PositionTypeToPositionID(type)); + [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(DeviceType, bool) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead.")] + public static bool IsDeviceConnected(DeviceType type, bool isLeft = true) => IsDeviceConnected(DeviceTypeToPositionType(type, isLeft)); + [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(PositionType) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead.")] + public static bool IsDeviceConnected(PositionType type) + => bHapticsLib.bHapticsManager.IsDeviceConnected(PositionTypeToPositionID(type)); - [Obsolete("MelonLoader.bHaptics.IsFeedbackRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPatternRegistered instead.")] - public static bool IsFeedbackRegistered(string key) - => bHapticsLib.bHapticsManager.IsPatternRegistered(key); + [Obsolete("MelonLoader.bHaptics.IsFeedbackRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPatternRegistered instead.")] + public static bool IsFeedbackRegistered(string key) + => bHapticsLib.bHapticsManager.IsPatternRegistered(key); - [Obsolete("MelonLoader.bHaptics.RegisterFeedback is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead.")] - public static void RegisterFeedback(string key, string tactFileStr) + [Obsolete("MelonLoader.bHaptics.RegisterFeedback is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead.")] + public static void RegisterFeedback(string key, string tactFileStr) + { + var proxyArray = new TinyJSON.ProxyArray { - TinyJSON.ProxyArray proxyArray = new TinyJSON.ProxyArray(); - proxyArray["project"] = TinyJSON.Decoder.Decode(tactFileStr); - bHapticsLib.bHapticsManager.RegisterPatternFromJson(key, TinyJSON.Encoder.Encode(proxyArray)); - } + ["project"] = TinyJSON.Decoder.Decode(tactFileStr) + }; + bHapticsLib.bHapticsManager.RegisterPatternFromJson(key, TinyJSON.Encoder.Encode(proxyArray)); + } - [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFile is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead.")] - public static void RegisterFeedbackFromTactFile(string key, string tactFileStr) - => bHapticsLib.bHapticsManager.RegisterPatternFromJson(key, tactFileStr); - [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFileReflected is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternSwappedFromJson instead.")] - public static void RegisterFeedbackFromTactFileReflected(string key, string tactFileStr) - => bHapticsLib.bHapticsManager.RegisterPatternSwappedFromJson(key, tactFileStr); + [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFile is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead.")] + public static void RegisterFeedbackFromTactFile(string key, string tactFileStr) + => bHapticsLib.bHapticsManager.RegisterPatternFromJson(key, tactFileStr); + [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFileReflected is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternSwappedFromJson instead.")] + public static void RegisterFeedbackFromTactFileReflected(string key, string tactFileStr) + => bHapticsLib.bHapticsManager.RegisterPatternSwappedFromJson(key, tactFileStr); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] - public static void SubmitRegistered(string key) - => bHapticsLib.bHapticsManager.PlayRegistered(key); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] - public static void SubmitRegistered(string key, int startTimeMillis) - => bHapticsLib.bHapticsManager.PlayRegistered(key, startTimeMillis); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] - public static void SubmitRegistered(string key, string altKey, ScaleOption option) - => bHapticsLib.bHapticsManager.PlayRegistered(key, altKey, - new bHapticsLib.ScaleOption { Duration = option.Duration, Intensity = option.Intensity }); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] - public static void SubmitRegistered(string key, string altKey, ScaleOption sOption, RotationOption rOption) - => bHapticsLib.bHapticsManager.PlayRegistered(key, altKey, - new bHapticsLib.ScaleOption { Duration = sOption.Duration, Intensity = sOption.Intensity }, - new bHapticsLib.RotationOption { OffsetAngleX = rOption.OffsetX, OffsetY = rOption.OffsetY }); + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + public static void SubmitRegistered(string key) + => bHapticsLib.bHapticsManager.PlayRegistered(key); + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + public static void SubmitRegistered(string key, int startTimeMillis) + => bHapticsLib.bHapticsManager.PlayRegistered(key, startTimeMillis); + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + public static void SubmitRegistered(string key, string altKey, ScaleOption option) + => bHapticsLib.bHapticsManager.PlayRegistered(key, altKey, + new bHapticsLib.ScaleOption { Duration = option.Duration, Intensity = option.Intensity }); + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + public static void SubmitRegistered(string key, string altKey, ScaleOption sOption, RotationOption rOption) + => bHapticsLib.bHapticsManager.PlayRegistered(key, altKey, + new bHapticsLib.ScaleOption { Duration = sOption.Duration, Intensity = sOption.Intensity }, + new bHapticsLib.RotationOption { OffsetAngleX = rOption.OffsetX, OffsetY = rOption.OffsetY }); - [Obsolete("MelonLoader.bHaptics.TurnOff is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlayingAll instead.")] - public static void TurnOff() - => bHapticsLib.bHapticsManager.StopPlayingAll(); - [Obsolete("MelonLoader.bHaptics.TurnOff(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlaying instead.")] - public static void TurnOff(string key) - => bHapticsLib.bHapticsManager.StopPlaying(key); + [Obsolete("MelonLoader.bHaptics.TurnOff is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlayingAll instead.")] + public static void TurnOff() + => bHapticsLib.bHapticsManager.StopPlayingAll(); + [Obsolete("MelonLoader.bHaptics.TurnOff(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlaying instead.")] + public static void TurnOff(string key) + => bHapticsLib.bHapticsManager.StopPlaying(key); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] - public static void Submit(string key, DeviceType type, bool isLeft, byte[] bytes, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), bytes, durationMillis); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] - public static void Submit(string key, PositionType position, byte[] bytes, int durationMillis) - => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), bytes); + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + public static void Submit(string key, DeviceType type, bool isLeft, byte[] bytes, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), bytes, durationMillis); + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + public static void Submit(string key, PositionType position, byte[] bytes, int durationMillis) + => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), bytes); + private static readonly Converter DotPointConverter = new((x) + => new bHapticsLib.DotPoint + { + Index = x.Index, + Intensity = x.Intensity + }); + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + public static void Submit(string key, DeviceType type, bool isLeft, List points, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), points, durationMillis); + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + public static void Submit(string key, PositionType position, List points, int durationMillis) + => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), points.ConvertAll(DotPointConverter)); - private static Converter DotPointConverter = new Converter((x) - => new bHapticsLib.DotPoint - { - Index = x.Index, - Intensity = x.Intensity - }); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] - public static void Submit(string key, DeviceType type, bool isLeft, List points, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), points, durationMillis); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] - public static void Submit(string key, PositionType position, List points, int durationMillis) - => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), points.ConvertAll(DotPointConverter)); + private static readonly Converter PathPointConverter = new((x) + => new bHapticsLib.PathPoint + { + X = x.X, + Y = x.Y, + Intensity = x.Intensity, + MotorCount = x.MotorCount + }); + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + public static void Submit(string key, DeviceType type, bool isLeft, List points, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), points, durationMillis); + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + public static void Submit(string key, PositionType position, List points, int durationMillis) + => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), (bHapticsLib.DotPoint[])null, points.ConvertAll(PathPointConverter)); + [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead.")] + public static FeedbackStatus GetCurrentFeedbackStatus(DeviceType type, bool isLeft = true) => GetCurrentFeedbackStatus(DeviceTypeToPositionType(type, isLeft)); + [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead.")] + public static FeedbackStatus GetCurrentFeedbackStatus(PositionType pos) + => new() + { values = bHapticsLib.bHapticsManager.GetDeviceStatus(PositionTypeToPositionID(pos)) }; - private static Converter PathPointConverter = new Converter((x) - => new bHapticsLib.PathPoint - { - X = x.X, - Y = x.Y, - Intensity = x.Intensity, - MotorCount = x.MotorCount - }); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] - public static void Submit(string key, DeviceType type, bool isLeft, List points, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), points, durationMillis); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] - public static void Submit(string key, PositionType position, List points, int durationMillis) - => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), (bHapticsLib.DotPoint[])null, points.ConvertAll(PathPointConverter)); + [Obsolete("MelonLoader.bHaptics.DeviceTypeToPositionType is Only Here for Compatibility Reasons.")] + public static PositionType DeviceTypeToPositionType(DeviceType pos, bool isLeft = true) + => pos switch + { + DeviceType.Tactal => PositionType.Head, + DeviceType.TactSuit => PositionType.Vest, + DeviceType.Tactosy_arms => isLeft ? PositionType.ForearmL : PositionType.ForearmR, + DeviceType.Tactosy_feet => isLeft ? PositionType.FootL : PositionType.FootR, + DeviceType.Tactosy_hands => isLeft ? PositionType.HandL : PositionType.HandR, + _ => PositionType.Head + }; - [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead.")] - public static FeedbackStatus GetCurrentFeedbackStatus(DeviceType type, bool isLeft = true) => GetCurrentFeedbackStatus(DeviceTypeToPositionType(type, isLeft)); - [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead.")] - public static FeedbackStatus GetCurrentFeedbackStatus(PositionType pos) - => new FeedbackStatus { values = bHapticsLib.bHapticsManager.GetDeviceStatus(PositionTypeToPositionID(pos)) }; + [Obsolete("MelonLoader.bHaptics.DeviceType is Only Here for Compatibility Reasons.")] + public enum DeviceType + { + None = 0, + Tactal = 1, + TactSuit = 2, + Tactosy_arms = 3, + Tactosy_hands = 4, + Tactosy_feet = 5 + } - [Obsolete("MelonLoader.bHaptics.DeviceTypeToPositionType is Only Here for Compatibility Reasons.")] - public static PositionType DeviceTypeToPositionType(DeviceType pos, bool isLeft = true) - => (pos) switch - { - DeviceType.Tactal => PositionType.Head, - DeviceType.TactSuit => PositionType.Vest, - DeviceType.Tactosy_arms => isLeft ? PositionType.ForearmL : PositionType.ForearmR, - DeviceType.Tactosy_feet => isLeft ? PositionType.FootL : PositionType.FootR, - DeviceType.Tactosy_hands => isLeft ? PositionType.HandL : PositionType.HandR, - _ => PositionType.Head - }; + [Obsolete("MelonLoader.bHaptics.PositionType is Only Here for Compatibility Reasons. Please use bHapticsLib.PositionID instead.")] + public enum PositionType + { + All = 0, + Left = 1, + Right = 2, + Vest = 3, + Head = 4, + Racket = 5, + HandL = 6, + HandR = 7, + FootL = 8, + FootR = 9, + ForearmL = 10, + ForearmR = 11, + VestFront = 201, + VestBack = 202, + GloveLeft = 203, + GloveRight = 204, + Custom1 = 251, + Custom2 = 252, + Custom3 = 253, + Custom4 = 254 + } - [Obsolete("MelonLoader.bHaptics.DeviceType is Only Here for Compatibility Reasons.")] - public enum DeviceType - { - None = 0, - Tactal = 1, - TactSuit = 2, - Tactosy_arms = 3, - Tactosy_hands = 4, - Tactosy_feet = 5 - } + [Obsolete("MelonLoader.bHaptics.RotationOption is Only Here for Compatibility Reasons. Please use bHapticsLib.RotationOption instead.")] + public class RotationOption + { + public float OffsetX, OffsetY; - [Obsolete("MelonLoader.bHaptics.PositionType is Only Here for Compatibility Reasons. Please use bHapticsLib.PositionID instead.")] - public enum PositionType + public RotationOption(float offsetX, float offsetY) { - All = 0, - Left = 1, - Right = 2, - Vest = 3, - Head = 4, - Racket = 5, - HandL = 6, - HandR = 7, - FootL = 8, - FootR = 9, - ForearmL = 10, - ForearmR = 11, - VestFront = 201, - VestBack = 202, - GloveLeft = 203, - GloveRight = 204, - Custom1 = 251, - Custom2 = 252, - Custom3 = 253, - Custom4 = 254 + OffsetX = offsetX; + OffsetY = offsetY; } - [Obsolete("MelonLoader.bHaptics.RotationOption is Only Here for Compatibility Reasons. Please use bHapticsLib.RotationOption instead.")] - public class RotationOption - { - public float OffsetX, OffsetY; - - public RotationOption(float offsetX, float offsetY) - { - OffsetX = offsetX; - OffsetY = offsetY; - } + public override string ToString() => "RotationOption { OffsetX=" + OffsetX.ToString() + + ", OffsetY=" + OffsetY.ToString() + " }"; + } - public override string ToString() => "RotationOption { OffsetX=" + OffsetX.ToString() + - ", OffsetY=" + OffsetY.ToString() + " }"; - } + [Obsolete("MelonLoader.bHaptics.ScaleOption is Only Here for Compatibility Reasons. Please use bHapticsLib.ScaleOption instead.")] + public class ScaleOption + { + public float Intensity, Duration; - [Obsolete("MelonLoader.bHaptics.ScaleOption is Only Here for Compatibility Reasons. Please use bHapticsLib.ScaleOption instead.")] - public class ScaleOption + public ScaleOption(float intensity = 1f, float duration = 1f) { - public float Intensity, Duration; + Intensity = intensity; + Duration = duration; + } - public ScaleOption(float intensity = 1f, float duration = 1f) - { - Intensity = intensity; - Duration = duration; - } + public override string ToString() => "ScaleOption { Intensity=" + Intensity.ToString() + + ", Duration=" + Duration.ToString() + " }"; + } - public override string ToString() => "ScaleOption { Intensity=" + Intensity.ToString() + - ", Duration=" + Duration.ToString() + " }"; - } + [Obsolete("MelonLoader.bHaptics.DotPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.DotPoint instead.")] + public class DotPoint + { + public int Index, Intensity; - [Obsolete("MelonLoader.bHaptics.DotPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.DotPoint instead.")] - public class DotPoint + public DotPoint(int index, int intensity = 50) { - public int Index, Intensity; + if (index is < 0 or > 19) + throw new Exception("Invalid argument index : " + index); + Intensity = MelonUtils.Clamp(intensity, 0, 100); + Index = index; + } - public DotPoint(int index, int intensity = 50) - { - if ((index < 0) || (index > 19)) - throw new Exception("Invalid argument index : " + index); - Intensity = MelonUtils.Clamp(intensity, 0, 100); - Index = index; - } + public override string ToString() => "DotPoint { Index=" + Index.ToString() + + ", Intensity=" + Intensity.ToString() + " }"; + } - public override string ToString() => "DotPoint { Index=" + Index.ToString() + - ", Intensity=" + Intensity.ToString() + " }"; - } + [Obsolete("MelonLoader.bHaptics.PathPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.PathPoint instead.")] + [StructLayout(LayoutKind.Sequential)] + public struct PathPoint + { + public float X, Y; + public int Intensity; + public int MotorCount; - [Obsolete("MelonLoader.bHaptics.PathPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.PathPoint instead.")] - [StructLayout(LayoutKind.Sequential)] - public struct PathPoint + public PathPoint(float x, float y, int intensity = 50, int motorCount = 3) { - public float X, Y; - public int Intensity; - public int MotorCount; + X = MelonUtils.Clamp(x, 0f, 1f); + Y = MelonUtils.Clamp(y, 0f, 1f); + Intensity = MelonUtils.Clamp(intensity, 0, 100); + MotorCount = MelonUtils.Clamp(motorCount, 0, 3); + } - public PathPoint(float x, float y, int intensity = 50, int motorCount = 3) - { - X = MelonUtils.Clamp(x, 0f, 1f); - Y = MelonUtils.Clamp(y, 0f, 1f); - Intensity = MelonUtils.Clamp(intensity, 0, 100); - MotorCount = MelonUtils.Clamp(motorCount, 0, 3); - } + public override string ToString() => "PathPoint { X=" + X.ToString() + + ", Y=" + Y.ToString() + + ", MotorCount=" + MotorCount.ToString() + + ", Intensity=" + Intensity.ToString() + " }"; + } - public override string ToString() => "PathPoint { X=" + X.ToString() + - ", Y=" + Y.ToString() + - ", MotorCount=" + MotorCount.ToString() + - ", Intensity=" + Intensity.ToString() + " }"; - } + [Obsolete("MelonLoader.bHaptics.FeedbackStatus is Only Here for Compatibility Reasons.")] + [StructLayout(LayoutKind.Sequential)] + public struct FeedbackStatus + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public int[] values; + }; - [Obsolete("MelonLoader.bHaptics.FeedbackStatus is Only Here for Compatibility Reasons.")] - [StructLayout(LayoutKind.Sequential)] - public struct FeedbackStatus + private static bHapticsLib.PositionID PositionTypeToPositionID(PositionType pos) + => pos switch { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public int[] values; + //PositionType.All => bHapticsLib.PositionID.All, + //PositionType.Left => bHapticsLib.PositionID.Left, + //PositionType.Right => bHapticsLib.PositionID.Right, + PositionType.Vest => bHapticsLib.PositionID.Vest, + PositionType.Head => bHapticsLib.PositionID.Head, + //PositionType.Racket => bHapticsLib.PositionID.Racket, + PositionType.HandL => bHapticsLib.PositionID.HandLeft, + PositionType.HandR => bHapticsLib.PositionID.HandRight, + PositionType.FootL => bHapticsLib.PositionID.FootLeft, + PositionType.FootR => bHapticsLib.PositionID.FootRight, + PositionType.ForearmL => bHapticsLib.PositionID.ArmLeft, + PositionType.ForearmR => bHapticsLib.PositionID.ArmRight, + PositionType.VestFront => bHapticsLib.PositionID.VestFront, + PositionType.VestBack => bHapticsLib.PositionID.VestBack, + PositionType.GloveLeft => bHapticsLib.PositionID.GloveLeft, + PositionType.GloveRight => bHapticsLib.PositionID.GloveRight, + //PositionType.Custom1 => bHapticsLib.PositionID.Custom1, + //PositionType.Custom2 => bHapticsLib.PositionID.Custom2, + //PositionType.Custom3 => bHapticsLib.PositionID.Custom3, + //PositionType.Custom4 => bHapticsLib.PositionID.Custom4, + _ => bHapticsLib.PositionID.Head }; - - private static bHapticsLib.PositionID PositionTypeToPositionID(PositionType pos) - => (pos) switch - { - //PositionType.All => bHapticsLib.PositionID.All, - //PositionType.Left => bHapticsLib.PositionID.Left, - //PositionType.Right => bHapticsLib.PositionID.Right, - PositionType.Vest => bHapticsLib.PositionID.Vest, - PositionType.Head => bHapticsLib.PositionID.Head, - //PositionType.Racket => bHapticsLib.PositionID.Racket, - PositionType.HandL => bHapticsLib.PositionID.HandLeft, - PositionType.HandR => bHapticsLib.PositionID.HandRight, - PositionType.FootL => bHapticsLib.PositionID.FootLeft, - PositionType.FootR => bHapticsLib.PositionID.FootRight, - PositionType.ForearmL => bHapticsLib.PositionID.ArmLeft, - PositionType.ForearmR => bHapticsLib.PositionID.ArmRight, - PositionType.VestFront => bHapticsLib.PositionID.VestFront, - PositionType.VestBack => bHapticsLib.PositionID.VestBack, - PositionType.GloveLeft => bHapticsLib.PositionID.GloveLeft, - PositionType.GloveRight => bHapticsLib.PositionID.GloveRight, - //PositionType.Custom1 => bHapticsLib.PositionID.Custom1, - //PositionType.Custom2 => bHapticsLib.PositionID.Custom2, - //PositionType.Custom3 => bHapticsLib.PositionID.Custom3, - //PositionType.Custom4 => bHapticsLib.PositionID.Custom4, - _ => bHapticsLib.PositionID.Head - }; - } } \ No newline at end of file diff --git a/MelonLoader/Core.cs b/MelonLoader/Core.cs index b2eb84c33..005cc0dd9 100644 --- a/MelonLoader/Core.cs +++ b/MelonLoader/Core.cs @@ -1,223 +1,220 @@ -using System; -using System.Diagnostics; -using System.Reflection; -using System.IO; -using bHapticsLib; -using System.Threading; -using MelonLoader.Resolver; -using MelonLoader.Utils; +using bHapticsLib; using MelonLoader.InternalUtils; using MelonLoader.Properties; +using MelonLoader.Resolver; +using MelonLoader.Utils; +using System; +using System.Diagnostics; +using System.Threading; [assembly: MelonLoader.PatchShield] #pragma warning disable IDE0051 // Prevent the IDE from complaining about private unreferenced methods -namespace MelonLoader +namespace MelonLoader; + +internal static class Core { - internal static class Core - { - private static bool _success = true; + private static bool _success = true; - internal static HarmonyLib.Harmony HarmonyInstance; - internal static bool Is_ALPHA_PreRelease = false; + internal static HarmonyLib.Harmony HarmonyInstance; + internal static bool Is_ALPHA_PreRelease = false; - internal static int Initialize() - { - // The config should be set before running anything else due to static constructors depending on it - // Don't ask me how this works, because I don't know either. -slxdy - var config = new LoaderConfig(); - BootstrapInterop.Library.GetLoaderConfig(ref config); - LoaderConfig.Current = config; + internal static int Initialize() + { + // The config should be set before running anything else due to static constructors depending on it + // Don't ask me how this works, because I don't know either. -slxdy + var config = new LoaderConfig(); + BootstrapInterop.Library.GetLoaderConfig(ref config); + LoaderConfig.Current = config; - MelonLaunchOptions.Load(); + MelonLaunchOptions.Load(); #if NET35 - // Disabled for now because of issues - //Net20Compatibility.TryInstall(); + // Disabled for now because of issues + //Net20Compatibility.TryInstall(); #endif - MelonUtils.SetupWineCheck(); + MelonUtils.SetupWineCheck(); - if (MelonUtils.IsUnderWineOrSteamProton()) - Pastel.ConsoleExtensions.Disable(); + if (MelonUtils.IsUnderWineOrSteamProton()) + Pastel.ConsoleExtensions.Disable(); - Fixes.UnhandledException.Install(AppDomain.CurrentDomain); - Fixes.ServerCertificateValidation.Install(); - Assertions.LemonAssertMapping.Setup(); + Fixes.UnhandledException.Install(AppDomain.CurrentDomain); + Fixes.ServerCertificateValidation.Install(); + Assertions.LemonAssertMapping.Setup(); - MelonUtils.Setup(AppDomain.CurrentDomain); - BootstrapInterop.SetDefaultConsoleTitleWithGameName(UnityInformationHandler.GameName, - UnityInformationHandler.GameVersion); + MelonUtils.Setup(AppDomain.CurrentDomain); + BootstrapInterop.SetDefaultConsoleTitleWithGameName(UnityInformationHandler.GameName, + UnityInformationHandler.GameVersion); - MelonAssemblyResolver.Setup(); + MelonAssemblyResolver.Setup(); #if NET6_0_OR_GREATER - if (LoaderConfig.Current.Loader.LaunchDebugger && MelonEnvironment.IsDotnetRuntime) - { - MelonLogger.Msg("[Init] User requested debugger, attempting to launch now..."); - Debugger.Launch(); - } + if (LoaderConfig.Current.Loader.LaunchDebugger && MelonEnvironment.IsDotnetRuntime) + { + MelonLogger.Msg("[Init] User requested debugger, attempting to launch now..."); + Debugger.Launch(); + } - Environment.SetEnvironmentVariable("IL2CPP_INTEROP_DATABASES_LOCATION", MelonEnvironment.Il2CppAssembliesDirectory); + Environment.SetEnvironmentVariable("IL2CPP_INTEROP_DATABASES_LOCATION", MelonEnvironment.Il2CppAssembliesDirectory); #else - try - { - if (!MonoLibrary.Setup()) - { - _success = false; - return 1; - } - } - catch (Exception ex) + try + { + if (!MonoLibrary.Setup()) { - MelonDebug.Msg($"[MonoLibrary] Caught Exception: {ex}"); _success = false; return 1; } + } + catch (Exception ex) + { + MelonDebug.Msg($"[MonoLibrary] Caught Exception: {ex}"); + _success = false; + return 1; + } #endif - HarmonyInstance = new HarmonyLib.Harmony(BuildInfo.Name); - Fixes.DetourContextDisposeFix.Install(); + HarmonyInstance = new HarmonyLib.Harmony(BuildInfo.Name); + Fixes.DetourContextDisposeFix.Install(); #if NET6_0_OR_GREATER - // if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - // NativeStackWalk.LogNativeStackTrace(); + // if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + // NativeStackWalk.LogNativeStackTrace(); - Fixes.DotnetAssemblyLoadContextFix.Install(); - Fixes.DotnetModHandlerRedirectionFix.Install(); + Fixes.DotnetAssemblyLoadContextFix.Install(); + Fixes.DotnetModHandlerRedirectionFix.Install(); #endif - Fixes.ForcedCultureInfo.Install(); - Fixes.InstancePatchFix.Install(); - Fixes.ProcessFix.Install(); + Fixes.ForcedCultureInfo.Install(); + Fixes.InstancePatchFix.Install(); + Fixes.ProcessFix.Install(); #if NET6_0_OR_GREATER - Fixes.AsmResolverFix.Install(); - Fixes.Il2CppInteropFixes.Install(); - Fixes.Il2CppICallInjector.Install(); + Fixes.AsmResolverFix.Install(); + Fixes.Il2CppInteropFixes.Install(); + Fixes.Il2CppICallInjector.Install(); #endif - PatchShield.Install(); + PatchShield.Install(); - MelonPreferences.Load(); + MelonPreferences.Load(); - MelonCompatibilityLayer.LoadModules(); + MelonCompatibilityLayer.LoadModules(); - bHapticsManager.Connect(BuildInfo.Name, UnityInformationHandler.GameName); - - MelonHandler.LoadUserlibs(MelonEnvironment.UserLibsDirectory); - MelonHandler.LoadMelonsFromDirectory(MelonEnvironment.PluginsDirectory); + bHapticsManager.Connect(BuildInfo.Name, UnityInformationHandler.GameName); - MelonEvents.MelonHarmonyEarlyInit.Invoke(); - MelonEvents.OnPreInitialization.Invoke(); + MelonHandler.LoadUserlibs(MelonEnvironment.UserLibsDirectory); + MelonHandler.LoadMelonsFromDirectory(MelonEnvironment.PluginsDirectory); - return 0; - } + MelonEvents.MelonHarmonyEarlyInit.Invoke(); + MelonEvents.OnPreInitialization.Invoke(); - private static int PreSetup() - { + return 0; + } + + private static int PreSetup() + { #if NET6_0_OR_GREATER - if (_success) - _success = Il2CppAssemblyGenerator.Run(); + if (_success) + _success = Il2CppAssemblyGenerator.Run(); #endif - return _success ? 0 : 1; - } + return _success ? 0 : 1; + } - internal static bool Start() - { - MelonEvents.OnApplicationEarlyStart.Invoke(); - MelonStartScreen.LoadAndRun(PreSetup); + internal static bool Start() + { + MelonEvents.OnApplicationEarlyStart.Invoke(); + MelonStartScreen.LoadAndRun(PreSetup); - if (!_success) - return false; + if (!_success) + return false; - MelonEvents.OnPreModsLoaded.Invoke(); - MelonHandler.LoadMelonsFromDirectory(MelonEnvironment.ModsDirectory); + MelonEvents.OnPreModsLoaded.Invoke(); + MelonHandler.LoadMelonsFromDirectory(MelonEnvironment.ModsDirectory); - MelonEvents.OnPreSupportModule.Invoke(); - if (!SupportModule.Setup()) - return false; + MelonEvents.OnPreSupportModule.Invoke(); + if (!SupportModule.Setup()) + return false; - AddUnityDebugLog(); + AddUnityDebugLog(); #if NET6_0_OR_GREATER - RegisterTypeInIl2Cpp.SetReady(); - RegisterTypeInIl2CppWithInterfaces.SetReady(); + RegisterTypeInIl2Cpp.SetReady(); + RegisterTypeInIl2CppWithInterfaces.SetReady(); #endif - MelonEvents.MelonHarmonyInit.Invoke(); - MelonEvents.OnApplicationStart.Invoke(); + MelonEvents.MelonHarmonyInit.Invoke(); + MelonEvents.OnApplicationStart.Invoke(); - return true; - } - - internal static string GetVersionString() - { - var lemon = LoaderConfig.Current.Loader.Theme == LoaderConfig.CoreConfig.LoaderTheme.Lemon; - var versionStr = $"{(lemon ? "Lemon" : "Melon")}Loader " + - $"v{BuildInfo.Version} " + - $"{(Is_ALPHA_PreRelease ? "ALPHA Pre-Release" : "Open-Beta")}"; - return versionStr; - } - - internal static void WelcomeMessage() - { - //if (MelonDebug.IsEnabled()) - MelonLogger.WriteSpacer(); - - MelonLogger.MsgDirect("------------------------------"); - MelonLogger.MsgDirect(GetVersionString()); - MelonLogger.MsgDirect($"OS: {MelonUtils.GetOSVersion()}"); - MelonLogger.MsgDirect($"Hash Code: {MelonUtils.HashCode}"); - MelonLogger.MsgDirect("------------------------------"); - var typeString = MelonUtils.IsGameIl2Cpp() ? "Il2cpp" : MelonUtils.IsOldMono() ? "Mono" : "MonoBleedingEdge"; - MelonLogger.MsgDirect($"Game Type: {typeString}"); - var archString = MelonUtils.IsGame32Bit() ? "x86" : "x64"; - MelonLogger.MsgDirect($"Game Arch: {archString}"); - MelonLogger.MsgDirect("------------------------------"); - MelonLogger.MsgDirect("Command-Line: "); - foreach (var pair in MelonLaunchOptions.InternalArguments) - if (string.IsNullOrEmpty(pair.Value)) - MelonLogger.MsgDirect($" {pair.Key}"); - else - MelonLogger.MsgDirect($" {pair.Key} = {pair.Value}"); - MelonLogger.MsgDirect("------------------------------"); - MelonEnvironment.PrintEnvironment(); - } + return true; + } - internal static void Quit() - { - MelonDebug.Msg("[ML Core] Received Quit Request! Shutting down..."); - - MelonPreferences.Save(); + internal static string GetVersionString() + { + var lemon = LoaderConfig.Current.Loader.Theme == LoaderConfig.CoreConfig.LoaderTheme.Lemon; + var versionStr = $"{(lemon ? "Lemon" : "Melon")}Loader " + + $"v{BuildInfo.Version} " + + $"{(Is_ALPHA_PreRelease ? "ALPHA Pre-Release" : "Open-Beta")}"; + return versionStr; + } + + internal static void WelcomeMessage() + { + //if (MelonDebug.IsEnabled()) + MelonLogger.WriteSpacer(); + + MelonLogger.MsgDirect("------------------------------"); + MelonLogger.MsgDirect(GetVersionString()); + MelonLogger.MsgDirect($"OS: {MelonUtils.GetOSVersion()}"); + MelonLogger.MsgDirect($"Hash Code: {MelonUtils.HashCode}"); + MelonLogger.MsgDirect("------------------------------"); + var typeString = MelonUtils.IsGameIl2Cpp() ? "Il2cpp" : MelonUtils.IsOldMono() ? "Mono" : "MonoBleedingEdge"; + MelonLogger.MsgDirect($"Game Type: {typeString}"); + var archString = MelonUtils.IsGame32Bit() ? "x86" : "x64"; + MelonLogger.MsgDirect($"Game Arch: {archString}"); + MelonLogger.MsgDirect("------------------------------"); + MelonLogger.MsgDirect("Command-Line: "); + foreach (var pair in MelonLaunchOptions.InternalArguments) + if (string.IsNullOrEmpty(pair.Value)) + MelonLogger.MsgDirect($" {pair.Key}"); + else + MelonLogger.MsgDirect($" {pair.Key} = {pair.Value}"); + MelonLogger.MsgDirect("------------------------------"); + MelonEnvironment.PrintEnvironment(); + } + + internal static void Quit() + { + MelonDebug.Msg("[ML Core] Received Quit Request! Shutting down..."); - HarmonyInstance.UnpatchSelf(); - bHapticsManager.Disconnect(); + MelonPreferences.Save(); + + HarmonyInstance.UnpatchSelf(); + bHapticsManager.Disconnect(); #if NET6_0_OR_GREATER - Fixes.Il2CppInteropFixes.Shutdown(); - Fixes.Il2CppICallInjector.Shutdown(); + Fixes.Il2CppInteropFixes.Shutdown(); + Fixes.Il2CppICallInjector.Shutdown(); #endif - Thread.Sleep(200); + Thread.Sleep(200); - if (LoaderConfig.Current.Loader.ForceQuit) - Process.GetCurrentProcess().Kill(); - } + if (LoaderConfig.Current.Loader.ForceQuit) + Process.GetCurrentProcess().Kill(); + } - private static void AddUnityDebugLog() - { - var msg = "~ This Game has been MODIFIED using MelonLoader. DO NOT report any issues to the Game Developers! ~"; - var line = new string('-', msg.Length); - SupportModule.Interface.UnityDebugLog(line); - SupportModule.Interface.UnityDebugLog(msg); - SupportModule.Interface.UnityDebugLog(line); - } + private static void AddUnityDebugLog() + { + var msg = "~ This Game has been MODIFIED using MelonLoader. DO NOT report any issues to the Game Developers! ~"; + var line = new string('-', msg.Length); + SupportModule.Interface.UnityDebugLog(line); + SupportModule.Interface.UnityDebugLog(msg); + SupportModule.Interface.UnityDebugLog(line); } } \ No newline at end of file diff --git a/MelonLoader/EnumExtensions.cs b/MelonLoader/EnumExtensions.cs index 1c6a27c22..19b0218f9 100644 --- a/MelonLoader/EnumExtensions.cs +++ b/MelonLoader/EnumExtensions.cs @@ -1,31 +1,30 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +/// +/// Extentions for enums. +/// +public static class EnumExtensions { /// - /// Extentions for enums. + /// From: http://www.sambeauvois.be/blog/2011/08/enum-hasflag-method-extension-for-4-0-framework/ + /// A FX 3.5 way to mimic the FX4 "HasFlag" method. /// - public static class EnumExtensions + /// The tested enum. + /// The value to test. + /// True if the flag is set. Otherwise false. + public static bool HasFlag(this Enum variable, Enum value) { - /// - /// From: http://www.sambeauvois.be/blog/2011/08/enum-hasflag-method-extension-for-4-0-framework/ - /// A FX 3.5 way to mimic the FX4 "HasFlag" method. - /// - /// The tested enum. - /// The value to test. - /// True if the flag is set. Otherwise false. - public static bool HasFlag(this Enum variable, Enum value) + // check if from the same type. + if (variable.GetType() != value.GetType()) { - // check if from the same type. - if (variable.GetType() != value.GetType()) - { - throw new ArgumentException("The checked flag is not from the same type as the checked variable."); - } + throw new ArgumentException("The checked flag is not from the same type as the checked variable."); + } - ulong num = Convert.ToUInt64(value); - ulong num2 = Convert.ToUInt64(variable); + var num = Convert.ToUInt64(value); + var num2 = Convert.ToUInt64(variable); - return (num2 & num) == num; - } + return (num2 & num) == num; } } diff --git a/MelonLoader/Fixes/DetourContextDisposeFix.cs b/MelonLoader/Fixes/DetourContextDisposeFix.cs index c6d62e502..2442d264d 100644 --- a/MelonLoader/Fixes/DetourContextDisposeFix.cs +++ b/MelonLoader/Fixes/DetourContextDisposeFix.cs @@ -1,55 +1,54 @@ -using System; +using HarmonyLib; +using MonoMod.RuntimeDetour; +using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; -using MonoMod.RuntimeDetour; -using HarmonyLib; -namespace MelonLoader.Fixes +namespace MelonLoader.Fixes; + +// fixes: https://github.com/MonoMod/MonoMod/pull/102 +// based-on: https://github.com/Hamunii/DetourContext.Dispose_Fix/blob/main/DetourContext_Dispose_Fix.cs +internal static class DetourContextDisposeFix { - // fixes: https://github.com/MonoMod/MonoMod/pull/102 - // based-on: https://github.com/Hamunii/DetourContext.Dispose_Fix/blob/main/DetourContext_Dispose_Fix.cs - internal static class DetourContextDisposeFix - { - private static MethodInfo _dispose; - private static MethodInfo _disposeTranspiler; - private static FieldInfo _isDisposed; + private static MethodInfo _dispose; + private static MethodInfo _disposeTranspiler; + private static FieldInfo _isDisposed; - internal static void Install() - { - Type detourContextType = typeof(DetourContext); - Type thisType = typeof(DetourContextDisposeFix); + internal static void Install() + { + var detourContextType = typeof(DetourContext); + var thisType = typeof(DetourContextDisposeFix); - _isDisposed = detourContextType.GetField("IsDisposed", BindingFlags.NonPublic | BindingFlags.Instance); - _dispose = detourContextType.GetMethod("Dispose", BindingFlags.Public | BindingFlags.Instance); - _disposeTranspiler = thisType.GetMethod(nameof(DetourContext_Dispose_Transpiler), BindingFlags.NonPublic | BindingFlags.Static); + _isDisposed = detourContextType.GetField("IsDisposed", BindingFlags.NonPublic | BindingFlags.Instance); + _dispose = detourContextType.GetMethod("Dispose", BindingFlags.Public | BindingFlags.Instance); + _disposeTranspiler = thisType.GetMethod(nameof(DetourContext_Dispose_Transpiler), BindingFlags.NonPublic | BindingFlags.Static); - MelonDebug.Msg("Patching MonoMod DetourContext.Dispose..."); - Core.HarmonyInstance.Patch(_dispose, - null, - null, - new HarmonyMethod(_disposeTranspiler)); - } + MelonDebug.Msg("Patching MonoMod DetourContext.Dispose..."); + Core.HarmonyInstance.Patch(_dispose, + null, + null, + new HarmonyMethod(_disposeTranspiler)); + } - private static IEnumerable DetourContext_Dispose_Transpiler(IEnumerable instructions) + private static IEnumerable DetourContext_Dispose_Transpiler(IEnumerable instructions) + { + var found = false; + CodeInstruction lastInstruction = null; + foreach (var instruction in instructions) { - bool found = false; - CodeInstruction lastInstruction = null; - foreach (CodeInstruction instruction in instructions) + if (!found + && (lastInstruction != null) + && lastInstruction.LoadsField(_isDisposed) + && (instruction.opcode == OpCodes.Brtrue)) { - if (!found - && (lastInstruction != null) - && lastInstruction.LoadsField(_isDisposed) - && (instruction.opcode == OpCodes.Brtrue)) - { - found = true; - instruction.opcode = OpCodes.Brfalse_S; - MelonDebug.Msg("Patched MonoMod DetourContext.Dispose -> IsDisposed"); - } - - yield return instruction; - lastInstruction = instruction; + found = true; + instruction.opcode = OpCodes.Brfalse_S; + MelonDebug.Msg("Patched MonoMod DetourContext.Dispose -> IsDisposed"); } + + yield return instruction; + lastInstruction = instruction; } } } diff --git a/MelonLoader/Fixes/ForcedCultureInfo.cs b/MelonLoader/Fixes/ForcedCultureInfo.cs index 88b3e447c..add7eb09b 100644 --- a/MelonLoader/Fixes/ForcedCultureInfo.cs +++ b/MelonLoader/Fixes/ForcedCultureInfo.cs @@ -1,55 +1,69 @@ -using System; +using HarmonyLib; +using System; using System.Globalization; using System.Linq; using System.Reflection; using System.Threading; -using HarmonyLib; -namespace MelonLoader.Fixes +namespace MelonLoader.Fixes; + +internal static class ForcedCultureInfo { - internal static class ForcedCultureInfo - { - private static CultureInfo SelectedCultureInfo = CultureInfo.InvariantCulture; + private static readonly CultureInfo SelectedCultureInfo = CultureInfo.InvariantCulture; + + internal static void Install() + { + var PatchType = typeof(ForcedCultureInfo); + var PatchMethod_Get = PatchType.GetMethod("GetMethod", BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod(); + var PatchMethod_Set = PatchType.GetMethod("SetMethod", BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod(); + + try + { + var CultureInfoType = typeof(CultureInfo); + var ThreadType = typeof(Thread); - internal static void Install() - { - Type PatchType = typeof(ForcedCultureInfo); - HarmonyMethod PatchMethod_Get = PatchType.GetMethod("GetMethod", BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod(); - HarmonyMethod PatchMethod_Set = PatchType.GetMethod("SetMethod", BindingFlags.NonPublic | BindingFlags.Static).ToNewHarmonyMethod(); + foreach (var fieldInfo in ThreadType + .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) + .Where(x => x.FieldType == CultureInfoType)) + fieldInfo.SetValue(null, SelectedCultureInfo); - try + foreach (var propertyInfo in ThreadType + .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) + .Where(x => x.PropertyType == CultureInfoType)) { - Type CultureInfoType = typeof(CultureInfo); - Type ThreadType = typeof(Thread); - - foreach (FieldInfo fieldInfo in ThreadType - .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) - .Where(x => x.FieldType == CultureInfoType)) - fieldInfo.SetValue(null, SelectedCultureInfo); - - foreach (PropertyInfo propertyInfo in ThreadType - .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) - .Where(x => x.PropertyType == CultureInfoType)) - { - MethodInfo getMethod = propertyInfo.GetGetMethod(); - if (getMethod != null) - try { Core.HarmonyInstance.Patch(getMethod, PatchMethod_Get); } - catch (Exception ex) { MelonLogger.Warning($"Exception Patching Thread Get Method {propertyInfo.Name}: {ex}"); } - - MethodInfo setMethod = propertyInfo.GetSetMethod(); - if (setMethod != null) - try { Core.HarmonyInstance.Patch(setMethod, PatchMethod_Set); } - catch (Exception ex) { MelonLogger.Warning($"Exception Patching Thread Set Method {propertyInfo.Name}: {ex}"); } - } + var getMethod = propertyInfo.GetGetMethod(); + if (getMethod != null) + try + { + Core.HarmonyInstance.Patch(getMethod, PatchMethod_Get); + } + catch (Exception ex) + { + MelonLogger.Warning($"Exception Patching Thread Get Method {propertyInfo.Name}: {ex}"); + } + + var setMethod = propertyInfo.GetSetMethod(); + if (setMethod != null) + try + { + Core.HarmonyInstance.Patch(setMethod, PatchMethod_Set); + } + catch (Exception ex) + { + MelonLogger.Warning($"Exception Patching Thread Set Method {propertyInfo.Name}: {ex}"); + } } - catch (Exception ex) { MelonLogger.Warning($"ForcedCultureInfo Exception: {ex}"); } - } - - private static bool GetMethod(ref CultureInfo __result) - { - __result = SelectedCultureInfo; - return false; - } - private static bool SetMethod() => false; - } + } + catch (Exception ex) + { + MelonLogger.Warning($"ForcedCultureInfo Exception: {ex}"); + } + } + + private static bool GetMethod(ref CultureInfo __result) + { + __result = SelectedCultureInfo; + return false; + } + private static bool SetMethod() => false; } \ No newline at end of file diff --git a/MelonLoader/Fixes/InstancePatchFix.cs b/MelonLoader/Fixes/InstancePatchFix.cs index 6b63ffc89..d7a8e8adc 100644 --- a/MelonLoader/Fixes/InstancePatchFix.cs +++ b/MelonLoader/Fixes/InstancePatchFix.cs @@ -1,35 +1,35 @@ -using System; -using System.Reflection; -using HarmonyLib; +using HarmonyLib; using MonoMod.RuntimeDetour; +using System; +using System.Reflection; -namespace MelonLoader.Fixes +namespace MelonLoader.Fixes; + +internal static class InstancePatchFix { - internal static class InstancePatchFix + internal static void Install() { - internal static void Install() - { - Type instancePatchFixType = typeof(InstancePatchFix); - HarmonyMethod patchMethod = AccessTools.Method(instancePatchFixType, "PatchMethod").ToNewHarmonyMethod(); + var instancePatchFixType = typeof(InstancePatchFix); + var patchMethod = AccessTools.Method(instancePatchFixType, "PatchMethod").ToNewHarmonyMethod(); - try - { - Core.HarmonyInstance.Patch(AccessTools.Method("HarmonyLib.PatchFunctions:ReversePatch"), patchMethod); - Core.HarmonyInstance.Patch(AccessTools.Method("HarmonyLib.HarmonyMethod:ImportMethod"), patchMethod); - } - catch (Exception ex) { MelonLogger.Warning($"InstancePatchFix Exception: {ex}"); } + try + { + Core.HarmonyInstance.Patch(AccessTools.Method("HarmonyLib.PatchFunctions:ReversePatch"), patchMethod); + Core.HarmonyInstance.Patch(AccessTools.Method("HarmonyLib.HarmonyMethod:ImportMethod"), patchMethod); + } + catch (Exception ex) + { + MelonLogger.Warning($"InstancePatchFix Exception: {ex}"); + } - Hook.OnDetour += (detour, originalMethod, patchMethod, delegateTarget) => PatchMethod(patchMethod); - Detour.OnDetour += (detour, originalMethod, patchMethod) => PatchMethod(patchMethod); - } + Hook.OnDetour += (detour, originalMethod, patchMethod, delegateTarget) => PatchMethod(patchMethod); + Detour.OnDetour += (detour, originalMethod, patchMethod) => PatchMethod(patchMethod); + } - private static bool PatchMethod(MethodBase __0) - { - if (__0 == null) - throw new NullReferenceException("Patch Method"); - if ((__0 != null) && !__0.IsStatic) - throw new Exception("Patch Method must be a Static Method!"); - return true; - } - } + private static bool PatchMethod(MethodBase __0) + { + if (__0 == null) + throw new NullReferenceException("Patch Method"); + return (__0 != null) && !__0.IsStatic ? throw new Exception("Patch Method must be a Static Method!") : true; + } } \ No newline at end of file diff --git a/MelonLoader/Fixes/Net20Compatibility.cs b/MelonLoader/Fixes/Net20Compatibility.cs index ac3fc0aa1..e6a1db8d6 100644 --- a/MelonLoader/Fixes/Net20Compatibility.cs +++ b/MelonLoader/Fixes/Net20Compatibility.cs @@ -4,30 +4,29 @@ using System.Drawing; using System.Text.RegularExpressions; -namespace MelonLoader.Fixes +namespace MelonLoader.Fixes; + +internal static class Net20Compatibility { - internal static class Net20Compatibility + public static void TryInstall() { - public static void TryInstall() - { - if (Environment.Version.Major != 2) - return; + if (Environment.Version.Major != 2) + return; - MelonEvents.OnPreInitialization.Subscribe(OnPreInit, unsubscribeOnFirstInvocation: true); + MelonEvents.OnPreInitialization.Subscribe(OnPreInit, unsubscribeOnFirstInvocation: true); - Core.HarmonyInstance.Patch(AccessTools.Constructor(typeof(Regex), [typeof(string), typeof(RegexOptions)]), new(typeof(Net20Compatibility), nameof(RegexCtor))); - } + Core.HarmonyInstance.Patch(AccessTools.Constructor(typeof(Regex), [typeof(string), typeof(RegexOptions)]), new(typeof(Net20Compatibility), nameof(RegexCtor))); + } - private static void OnPreInit() - { - MelonLogger.MsgDirect(Color.Yellow, "The current game is running on .NET Framework 2.0, which is obsolete. Some universal Melons may run into unexpected errors."); - } + private static void OnPreInit() + { + MelonLogger.MsgDirect(Color.Yellow, "The current game is running on .NET Framework 2.0, which is obsolete. Some universal Melons may run into unexpected errors."); + } - private static void RegexCtor([HarmonyArgument(1)] ref RegexOptions options) - { - // Compiled regex is not supported and results in an exception - options &= ~RegexOptions.Compiled; - } + private static void RegexCtor([HarmonyArgument(1)] ref RegexOptions options) + { + // Compiled regex is not supported and results in an exception + options &= ~RegexOptions.Compiled; } } #endif \ No newline at end of file diff --git a/MelonLoader/Fixes/ProcessFix.cs b/MelonLoader/Fixes/ProcessFix.cs index e45571973..e338f8cfe 100644 --- a/MelonLoader/Fixes/ProcessFix.cs +++ b/MelonLoader/Fixes/ProcessFix.cs @@ -1,104 +1,105 @@ -using System; +using HarmonyLib; +using System; using System.Diagnostics; -using System.Reflection; using System.Runtime.InteropServices; using System.Text; -using HarmonyLib; -namespace MelonLoader.Fixes +namespace MelonLoader.Fixes; + +internal static class ProcessFix { - internal static class ProcessFix - { - private static MainWindowFinder mainWindowFinder; - - internal static void Install() - { - mainWindowFinder = new MainWindowFinder(); - - Type processFixType = typeof(ProcessFix); - Type processType = typeof(Process); - - try - { - Core.HarmonyInstance.Patch(AccessTools.PropertyGetter(processType, "MainWindowHandle"), AccessTools.Method(processFixType, "get_MainWindowHandle").ToNewHarmonyMethod()); - Core.HarmonyInstance.Patch(AccessTools.PropertyGetter(processType, "MainWindowTitle"), AccessTools.Method(processFixType, "get_MainWindowTitle").ToNewHarmonyMethod()); - } - catch (Exception ex) { MelonLogger.Warning($"ProcessFix Exception: {ex}"); } - } - - // Taken and Modified from .NET Framework's System.dll - private static bool get_MainWindowHandle(Process __instance, ref IntPtr __result) + private static MainWindowFinder mainWindowFinder; + + internal static void Install() + { + mainWindowFinder = new MainWindowFinder(); + + var processFixType = typeof(ProcessFix); + var processType = typeof(Process); + + try + { + Core.HarmonyInstance.Patch(AccessTools.PropertyGetter(processType, "MainWindowHandle"), AccessTools.Method(processFixType, "get_MainWindowHandle").ToNewHarmonyMethod()); + Core.HarmonyInstance.Patch(AccessTools.PropertyGetter(processType, "MainWindowTitle"), AccessTools.Method(processFixType, "get_MainWindowTitle").ToNewHarmonyMethod()); + } + catch (Exception ex) + { + MelonLogger.Warning($"ProcessFix Exception: {ex}"); + } + } + + // Taken and Modified from .NET Framework's System.dll + private static bool get_MainWindowHandle(Process __instance, ref IntPtr __result) + { + __result = mainWindowFinder.FindMainWindow(__instance.Id); + return false; + } + + // Taken and Modified from .NET Framework's System.dll + private static bool get_MainWindowTitle(Process __instance, ref string __result) + { + var intPtr = __instance.MainWindowHandle; + if (intPtr == IntPtr.Zero) + __result = string.Empty; + else + { + var capacity = GetWindowTextLength(new HandleRef(__instance, intPtr)) * 2; + var stringBuilder = new StringBuilder(capacity); + GetWindowText(new HandleRef(__instance, intPtr), stringBuilder, stringBuilder.Capacity); + __result = stringBuilder.ToString(); + } + return false; + } + + // Taken and Modified from .NET Framework's System.dll + [DllImport("user32.dll", BestFitMapping = true, CharSet = CharSet.Auto)] + private static extern int GetWindowText(HandleRef hWnd, StringBuilder lpString, int nMaxCount); + + // Taken and Modified from .NET Framework's System.dll + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern int GetWindowTextLength(HandleRef hWnd); + + // Taken and Modified from .NET Framework's System.dll + internal class MainWindowFinder + { + private IntPtr Handle; + private int ID; + + private bool EnumWindowsCallback(IntPtr handle, IntPtr extraParameter) { - __result = mainWindowFinder.FindMainWindow(__instance.Id); - return false; + int num; + GetWindowThreadProcessId(new HandleRef(this, handle), out num); + if (num != ID || !IsMainWindow(handle)) + return true; + Handle = handle; + return false; } - // Taken and Modified from .NET Framework's System.dll - private static bool get_MainWindowTitle(Process __instance, ref string __result) - { - IntPtr intPtr = __instance.MainWindowHandle; - if (intPtr == IntPtr.Zero) - __result = string.Empty; - else - { - int capacity = GetWindowTextLength(new HandleRef(__instance, intPtr)) * 2; - StringBuilder stringBuilder = new StringBuilder(capacity); - GetWindowText(new HandleRef(__instance, intPtr), stringBuilder, stringBuilder.Capacity); - __result = stringBuilder.ToString(); - } - return false; - } - - // Taken and Modified from .NET Framework's System.dll - [DllImport("user32.dll", BestFitMapping = true, CharSet = CharSet.Auto)] - private static extern int GetWindowText(HandleRef hWnd, StringBuilder lpString, int nMaxCount); - - // Taken and Modified from .NET Framework's System.dll - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern int GetWindowTextLength(HandleRef hWnd); - - // Taken and Modified from .NET Framework's System.dll - internal class MainWindowFinder + internal IntPtr FindMainWindow(int processId) { - private IntPtr Handle; - private int ID; - - private bool EnumWindowsCallback(IntPtr handle, IntPtr extraParameter) - { - int num; - GetWindowThreadProcessId(new HandleRef(this, handle), out num); - if (num != ID || !IsMainWindow(handle)) - return true; - Handle = handle; - return false; - } - - internal IntPtr FindMainWindow(int processId) - { - Handle = (IntPtr)0; - ID = processId; - EnumThreadWindowsCallback enumThreadWindowsCallback = new EnumThreadWindowsCallback(EnumWindowsCallback); - EnumWindows(enumThreadWindowsCallback, IntPtr.Zero); - GC.KeepAlive(enumThreadWindowsCallback); - return Handle; - } - - private bool IsMainWindow(IntPtr handle) - => (!(GetWindow(new HandleRef(this, handle), 4) != (IntPtr)0) && IsWindowVisible(new HandleRef(this, handle))); - - private delegate bool EnumThreadWindowsCallback(IntPtr hWnd, IntPtr lParam); - - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = false, SetLastError = true)] - private static extern int GetWindowThreadProcessId(HandleRef handle, out int processId); - - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = false, SetLastError = true)] - private static extern bool EnumWindows(EnumThreadWindowsCallback callback, IntPtr extraData); - - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - private static extern IntPtr GetWindow(HandleRef hWnd, int uCmd); - - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern bool IsWindowVisible(HandleRef hWnd); - } + Handle = (IntPtr)0; + ID = processId; + var enumThreadWindowsCallback = new EnumThreadWindowsCallback(EnumWindowsCallback); + EnumWindows(enumThreadWindowsCallback, IntPtr.Zero); + GC.KeepAlive(enumThreadWindowsCallback); + return Handle; + } + + private bool IsMainWindow(IntPtr handle) + => !(GetWindow(new HandleRef(this, handle), 4) != (IntPtr)0) && IsWindowVisible(new HandleRef(this, handle)); + + private delegate bool EnumThreadWindowsCallback(IntPtr hWnd, IntPtr lParam); + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = false, SetLastError = true)] + private static extern int GetWindowThreadProcessId(HandleRef handle, out int processId); + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = false, SetLastError = true)] + private static extern bool EnumWindows(EnumThreadWindowsCallback callback, IntPtr extraData); + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + private static extern IntPtr GetWindow(HandleRef hWnd, int uCmd); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool IsWindowVisible(HandleRef hWnd); } } \ No newline at end of file diff --git a/MelonLoader/Fixes/ServerCertificateValidation.cs b/MelonLoader/Fixes/ServerCertificateValidation.cs index 2f3211c2e..392408564 100644 --- a/MelonLoader/Fixes/ServerCertificateValidation.cs +++ b/MelonLoader/Fixes/ServerCertificateValidation.cs @@ -6,59 +6,59 @@ using System.Security.Cryptography.X509Certificates; #endif -namespace MelonLoader.Fixes +namespace MelonLoader.Fixes; + +internal static class ServerCertificateValidation { - internal static class ServerCertificateValidation - { #if !NET6_0 - internal static void Install() + internal static void Install() + { + try { - try - { - Type SPMType = typeof(ServicePointManager); + var SPMType = typeof(ServicePointManager); - // ServicePointManager.Expect100Continue - FieldInfo expectContinue = SPMType.GetField(nameof(expectContinue), BindingFlags.NonPublic | BindingFlags.Static); - if (expectContinue != null) - expectContinue.SetValue(null, true); + // ServicePointManager.Expect100Continue + FieldInfo expectContinue = SPMType.GetField(nameof(expectContinue), BindingFlags.NonPublic | BindingFlags.Static); + expectContinue?.SetValue(null, true); - //ServicePointManager.SecurityProtocol - FieldInfo _securityProtocol = SPMType.GetField(nameof(_securityProtocol), BindingFlags.NonPublic | BindingFlags.Static); - if (_securityProtocol != null) - _securityProtocol.SetValue(null, - SecurityProtocolType.Ssl3 - | SecurityProtocolType.Tls - | (SecurityProtocolType)768 /* SecurityProtocolType.Tls11 */ - | (SecurityProtocolType)3072 /* SecurityProtocolType.Tls12 */); + //ServicePointManager.SecurityProtocol + FieldInfo _securityProtocol = SPMType.GetField(nameof(_securityProtocol), BindingFlags.NonPublic | BindingFlags.Static); + _securityProtocol?.SetValue(null, + SecurityProtocolType.Ssl3 + | SecurityProtocolType.Tls + | (SecurityProtocolType)768 /* SecurityProtocolType.Tls11 */ + | (SecurityProtocolType)3072 /* SecurityProtocolType.Tls12 */); - ServicePointManager.ServerCertificateValidationCallback += CertificateValidation; - } - catch (Exception ex) { MelonLogger.Warning($"ServerCertificateValidation Exception: {ex}"); } + ServicePointManager.ServerCertificateValidationCallback += CertificateValidation; } - - // Based on: https://stackoverflow.com/questions/43457050/error-getting-response-stream-write-the-authentication-or-decryption-has-faile - private static bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + catch (Exception ex) { - if (sslPolicyErrors == SslPolicyErrors.None) - return true; - for (int i = 0; i < chain.ChainStatus.Length; i++) - { - if (chain.ChainStatus[i].Status == X509ChainStatusFlags.RevocationStatusUnknown) - continue; - chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain; - chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; - chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0); - chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags; - bool chainIsValid = chain.Build((X509Certificate2)certificate); - if (!chainIsValid) - return false; - } - return true; + MelonLogger.Warning($"ServerCertificateValidation Exception: {ex}"); } -#else - internal static void Install() + } + + // Based on: https://stackoverflow.com/questions/43457050/error-getting-response-stream-write-the-authentication-or-decryption-has-faile + private static bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors == SslPolicyErrors.None) + return true; + for (var i = 0; i < chain.ChainStatus.Length; i++) { + if (chain.ChainStatus[i].Status == X509ChainStatusFlags.RevocationStatusUnknown) + continue; + chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain; + chain.ChainPolicy.RevocationMode = X509RevocationMode.Online; + chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0); + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags; + var chainIsValid = chain.Build((X509Certificate2)certificate); + if (!chainIsValid) + return false; } -#endif + return true; } +#else + internal static void Install() + { + } +#endif } \ No newline at end of file diff --git a/MelonLoader/Fixes/UnhandledException.cs b/MelonLoader/Fixes/UnhandledException.cs index 04141615f..3596a33df 100644 --- a/MelonLoader/Fixes/UnhandledException.cs +++ b/MelonLoader/Fixes/UnhandledException.cs @@ -1,12 +1,11 @@ using System; -namespace MelonLoader.Fixes +namespace MelonLoader.Fixes; + +internal static class UnhandledException { - internal static class UnhandledException - { - internal static void Install(AppDomain domain) => - domain.UnhandledException += - (sender, args) => - MelonLogger.Error((args.ExceptionObject as Exception).ToString()); - } + internal static void Install(AppDomain domain) => + domain.UnhandledException += + (sender, args) => + MelonLogger.Error((args.ExceptionObject as Exception).ToString()); } \ No newline at end of file diff --git a/MelonLoader/HarmonyDontPatchAllAttribute.cs b/MelonLoader/HarmonyDontPatchAllAttribute.cs index 67d2ccc85..5dbba5496 100644 --- a/MelonLoader/HarmonyDontPatchAllAttribute.cs +++ b/MelonLoader/HarmonyDontPatchAllAttribute.cs @@ -1,7 +1,6 @@ using System; -namespace MelonLoader -{ - [AttributeUsage(AttributeTargets.Assembly)] - public class HarmonyDontPatchAllAttribute : Attribute { public HarmonyDontPatchAllAttribute() { } } -} \ No newline at end of file +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class HarmonyDontPatchAllAttribute : Attribute { public HarmonyDontPatchAllAttribute() { } } \ No newline at end of file diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs index 2a29a1de9..a110d7a69 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs @@ -1,79 +1,74 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2 +namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2; + +/// +/// An example class to demonstrate compression and decompression of BZip2 streams. +/// +public static class BZip2 { - /// - /// An example class to demonstrate compression and decompression of BZip2 streams. - /// - public static class BZip2 - { - /// - /// Decompress the input writing - /// uncompressed data to the output stream - /// - /// The readable stream containing data to decompress. - /// The output stream to receive the decompressed data. - /// Both streams are closed on completion if true. - public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner) - { - if (inStream == null) - throw new ArgumentNullException(nameof(inStream)); + /// + /// Decompress the input writing + /// uncompressed data to the output stream + /// + /// The readable stream containing data to decompress. + /// The output stream to receive the decompressed data. + /// Both streams are closed on completion if true. + public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner) + { + if (inStream == null) + throw new ArgumentNullException(nameof(inStream)); - if (outStream == null) - throw new ArgumentNullException(nameof(outStream)); + if (outStream == null) + throw new ArgumentNullException(nameof(outStream)); - try - { - using (BZip2InputStream bzipInput = new BZip2InputStream(inStream)) - { - bzipInput.IsStreamOwner = isStreamOwner; - Core.StreamUtils.Copy(bzipInput, outStream, new byte[4096]); - } - } - finally - { - if (isStreamOwner) - { - // inStream is closed by the BZip2InputStream if stream owner - outStream.Dispose(); - } - } - } + try + { + using var bzipInput = new BZip2InputStream(inStream); + bzipInput.IsStreamOwner = isStreamOwner; + Core.StreamUtils.Copy(bzipInput, outStream, new byte[4096]); + } + finally + { + if (isStreamOwner) + { + // inStream is closed by the BZip2InputStream if stream owner + outStream.Dispose(); + } + } + } - /// - /// Compress the input stream sending - /// result data to output stream - /// - /// The readable stream to compress. - /// The output stream to receive the compressed data. - /// Both streams are closed on completion if true. - /// Block size acts as compression level (1 to 9) with 1 giving - /// the lowest compression and 9 the highest. - public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int level) - { - if (inStream == null) - throw new ArgumentNullException(nameof(inStream)); + /// + /// Compress the input stream sending + /// result data to output stream + /// + /// The readable stream to compress. + /// The output stream to receive the compressed data. + /// Both streams are closed on completion if true. + /// Block size acts as compression level (1 to 9) with 1 giving + /// the lowest compression and 9 the highest. + public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int level) + { + if (inStream == null) + throw new ArgumentNullException(nameof(inStream)); - if (outStream == null) - throw new ArgumentNullException(nameof(outStream)); + if (outStream == null) + throw new ArgumentNullException(nameof(outStream)); - try - { - using (BZip2OutputStream bzipOutput = new BZip2OutputStream(outStream, level)) - { - bzipOutput.IsStreamOwner = isStreamOwner; - Core.StreamUtils.Copy(inStream, bzipOutput, new byte[4096]); - } - } - finally - { - if (isStreamOwner) - { - // outStream is closed by the BZip2OutputStream if stream owner - inStream.Dispose(); - } - } - } - } + try + { + using var bzipOutput = new BZip2OutputStream(outStream, level); + bzipOutput.IsStreamOwner = isStreamOwner; + Core.StreamUtils.Copy(inStream, bzipOutput, new byte[4096]); + } + finally + { + if (isStreamOwner) + { + // outStream is closed by the BZip2OutputStream if stream owner + inStream.Dispose(); + } + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs index 0f253f4c6..ccd4529bb 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs @@ -1,117 +1,116 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2 +namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2; + +/// +/// Defines internal values for both compression and decompression +/// +internal static class BZip2Constants { - /// - /// Defines internal values for both compression and decompression - /// - internal static class BZip2Constants - { - /// - /// Random numbers used to randomise repetitive blocks - /// - public readonly static int[] RandomNumbers = { - 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, - 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, - 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, - 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, - 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, - 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, - 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, - 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, - 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, - 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, - 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, - 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, - 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, - 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, - 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, - 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, - 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, - 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, - 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, - 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, - 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, - 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, - 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, - 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, - 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, - 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, - 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, - 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, - 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, - 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, - 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, - 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, - 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, - 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, - 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, - 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, - 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, - 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, - 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, - 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, - 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, - 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, - 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, - 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, - 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, - 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, - 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, - 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, - 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, - 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, - 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, - 936, 638 - }; + /// + /// Random numbers used to randomise repetitive blocks + /// + public static readonly int[] RandomNumbers = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; - /// - /// When multiplied by compression parameter (1-9) gives the block size for compression - /// 9 gives the best compression but uses the most memory. - /// - public const int BaseBlockSize = 100000; + /// + /// When multiplied by compression parameter (1-9) gives the block size for compression + /// 9 gives the best compression but uses the most memory. + /// + public const int BaseBlockSize = 100000; - /// - /// Backend constant - /// - public const int MaximumAlphaSize = 258; + /// + /// Backend constant + /// + public const int MaximumAlphaSize = 258; - /// - /// Backend constant - /// - public const int MaximumCodeLength = 23; + /// + /// Backend constant + /// + public const int MaximumCodeLength = 23; - /// - /// Backend constant - /// - public const int RunA = 0; + /// + /// Backend constant + /// + public const int RunA = 0; - /// - /// Backend constant - /// - public const int RunB = 1; + /// + /// Backend constant + /// + public const int RunB = 1; - /// - /// Backend constant - /// - public const int GroupCount = 6; + /// + /// Backend constant + /// + public const int GroupCount = 6; - /// - /// Backend constant - /// - public const int GroupSize = 50; + /// + /// Backend constant + /// + public const int GroupSize = 50; - /// - /// Backend constant - /// - public const int NumberOfIterations = 4; + /// + /// Backend constant + /// + public const int NumberOfIterations = 4; - /// - /// Backend constant - /// - public const int MaximumSelectors = (2 + (900000 / GroupSize)); + /// + /// Backend constant + /// + public const int MaximumSelectors = 2 + (900000 / GroupSize); - /// - /// Backend constant - /// - public const int OvershootBytes = 20; - } + /// + /// Backend constant + /// + public const int OvershootBytes = 20; } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs index 0c2d3b796..7d7f70896 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs @@ -1,54 +1,53 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2 +namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2; + +/// +/// BZip2Exception represents exceptions specific to BZip2 classes and code. +/// +[Serializable] +public class BZip2Exception : SharpZipBaseException { - /// - /// BZip2Exception represents exceptions specific to BZip2 classes and code. - /// - [Serializable] - public class BZip2Exception : SharpZipBaseException - { - /// - /// Initialise a new instance of . - /// - public BZip2Exception() - { - } + /// + /// Initialise a new instance of . + /// + public BZip2Exception() + { + } - /// - /// Initialise a new instance of with its message string. - /// - /// A that describes the error. - public BZip2Exception(string message) - : base(message) - { - } + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public BZip2Exception(string message) + : base(message) + { + } - /// - /// Initialise a new instance of . - /// - /// A that describes the error. - /// The that caused this exception. - public BZip2Exception(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public BZip2Exception(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the BZip2Exception class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected BZip2Exception(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the BZip2Exception class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected BZip2Exception(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs index b3abc04db..653d92e03 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs @@ -1,725 +1,723 @@ #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER - #define VECTORIZE_MEMORY_MOVE +#define VECTORIZE_MEMORY_MOVE #endif using MelonLoader.ICSharpCode.SharpZipLib.Checksum; using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2 +namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2; + +/// +/// An input stream that decompresses files in the BZip2 format +/// +public class BZip2InputStream : Stream { - /// - /// An input stream that decompresses files in the BZip2 format - /// - public class BZip2InputStream : Stream - { - #region Constants - - private const int START_BLOCK_STATE = 1; - private const int RAND_PART_A_STATE = 2; - private const int RAND_PART_B_STATE = 3; - private const int RAND_PART_C_STATE = 4; - private const int NO_RAND_PART_A_STATE = 5; - private const int NO_RAND_PART_B_STATE = 6; - private const int NO_RAND_PART_C_STATE = 7; + #region Constants + + private const int START_BLOCK_STATE = 1; + private const int RAND_PART_A_STATE = 2; + private const int RAND_PART_B_STATE = 3; + private const int RAND_PART_C_STATE = 4; + private const int NO_RAND_PART_A_STATE = 5; + private const int NO_RAND_PART_B_STATE = 6; + private const int NO_RAND_PART_C_STATE = 7; #if VECTORIZE_MEMORY_MOVE private static readonly int VectorSize = System.Numerics.Vector.Count; #endif // VECTORIZE_MEMORY_MOVE -#endregion Constants - - #region Instance Fields - - /*-- - index of the last char in the block, so - the block size == last + 1. - --*/ - private int last; - - /*-- - index in zptr[] of original string after sorting. - --*/ - private int origPtr; - - /*-- - always: in the range 0 .. 9. - The current block size is 100000 * this number. - --*/ - private int blockSize100k; - - private bool blockRandomised; - - private int bsBuff; - private int bsLive; - private IChecksum mCrc = new BZip2Crc(); - - private bool[] inUse = new bool[256]; - private int nInUse; - - private byte[] seqToUnseq = new byte[256]; - private byte[] unseqToSeq = new byte[256]; - - private byte[] selector = new byte[BZip2Constants.MaximumSelectors]; - private byte[] selectorMtf = new byte[BZip2Constants.MaximumSelectors]; - - private int[] tt; - private byte[] ll8; - - /*-- - freq table collected to save a pass over the data - during decompression. - --*/ - private int[] unzftab = new int[256]; - - private int[][] limit = new int[BZip2Constants.GroupCount][]; - private int[][] baseArray = new int[BZip2Constants.GroupCount][]; - private int[][] perm = new int[BZip2Constants.GroupCount][]; - private int[] minLens = new int[BZip2Constants.GroupCount]; - - private readonly Stream baseStream; - private bool streamEnd; - - private int currentChar = -1; - - private int currentState = START_BLOCK_STATE; - - private int storedBlockCRC, storedCombinedCRC; - private int computedBlockCRC; - private uint computedCombinedCRC; - - private int count, chPrev, ch2; - private int tPos; - private int rNToGo; - private int rTPos; - private int i2, j2; - private byte z; - - #endregion Instance Fields - - /// - /// Construct instance for reading from stream - /// - /// Data source - public BZip2InputStream(Stream stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - // init arrays - for (int i = 0; i < BZip2Constants.GroupCount; ++i) - { - limit[i] = new int[BZip2Constants.MaximumAlphaSize]; - baseArray[i] = new int[BZip2Constants.MaximumAlphaSize]; - perm[i] = new int[BZip2Constants.MaximumAlphaSize]; - } - - baseStream = stream; - bsLive = 0; - bsBuff = 0; - Initialize(); - InitBlock(); - SetupBlock(); - } - - /// - /// Get/set flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - public bool IsStreamOwner { get; set; } = true; - - #region Stream Overrides - - /// - /// Gets a value indicating if the stream supports reading - /// - public override bool CanRead - { - get - { - return baseStream.CanRead; - } - } - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - public override bool CanSeek - { - get - { - return false; - } - } - - /// - /// Gets a value indicating whether the current stream supports writing. - /// This property always returns false - /// - public override bool CanWrite - { - get - { - return false; - } - } - - /// - /// Gets the length in bytes of the stream. - /// - public override long Length - { - get - { - return baseStream.Length; - } - } - - /// - /// Gets the current position of the stream. - /// Setting the position is not supported and will throw a NotSupportException. - /// - /// Any attempt to set the position. - public override long Position - { - get - { - return baseStream.Position; - } - set - { - throw new NotSupportedException("BZip2InputStream position cannot be set"); - } - } - - /// - /// Flushes the stream. - /// - public override void Flush() - { - baseStream.Flush(); - } - - /// - /// Set the streams position. This operation is not supported and will throw a NotSupportedException - /// - /// A byte offset relative to the parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// The new position of the stream. - /// Any access - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("BZip2InputStream Seek not supported"); - } - - /// - /// Sets the length of this stream to the given value. - /// This operation is not supported and will throw a NotSupportedExceptionortedException - /// - /// The new length for the stream. - /// Any access - public override void SetLength(long value) - { - throw new NotSupportedException("BZip2InputStream SetLength not supported"); - } - - /// - /// Writes a block of bytes to this stream using data from a buffer. - /// This operation is not supported and will throw a NotSupportedException - /// - /// The buffer to source data from. - /// The offset to start obtaining data from. - /// The number of bytes of data to write. - /// Any access - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("BZip2InputStream Write not supported"); - } - - /// - /// Writes a byte to the current position in the file stream. - /// This operation is not supported and will throw a NotSupportedException - /// - /// The value to write. - /// Any access - public override void WriteByte(byte value) - { - throw new NotSupportedException("BZip2InputStream WriteByte not supported"); - } - - /// - /// Read a sequence of bytes and advances the read position by one byte. - /// - /// Array of bytes to store values in - /// Offset in array to begin storing data - /// The maximum number of bytes to read - /// The total number of bytes read into the buffer. This might be less - /// than the number of bytes requested if that number of bytes are not - /// currently available or zero if the end of the stream is reached. - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - for (int i = 0; i < count; ++i) - { - int rb = ReadByte(); - if (rb == -1) - { - return i; - } - buffer[offset + i] = (byte)rb; - } - return count; - } - - /// - /// Closes the stream, releasing any associated resources. - /// - protected override void Dispose(bool disposing) - { - if (disposing && IsStreamOwner) - { - baseStream.Dispose(); - } - } - - /// - /// Read a byte from stream advancing position - /// - /// byte read or -1 on end of stream - public override int ReadByte() - { - if (streamEnd) - { - return -1; // ok - } - - int retChar = currentChar; - switch (currentState) - { - case RAND_PART_B_STATE: - SetupRandPartB(); - break; - - case RAND_PART_C_STATE: - SetupRandPartC(); - break; - - case NO_RAND_PART_B_STATE: - SetupNoRandPartB(); - break; - - case NO_RAND_PART_C_STATE: - SetupNoRandPartC(); - break; - - case START_BLOCK_STATE: - case NO_RAND_PART_A_STATE: - case RAND_PART_A_STATE: - break; - } - return retChar; - } - - #endregion Stream Overrides - - private void MakeMaps() - { - nInUse = 0; - for (int i = 0; i < 256; ++i) - { - if (inUse[i]) - { - seqToUnseq[nInUse] = (byte)i; - unseqToSeq[i] = (byte)nInUse; - nInUse++; - } - } - } - - private void Initialize() - { - char magic1 = BsGetUChar(); - char magic2 = BsGetUChar(); - - char magic3 = BsGetUChar(); - char magic4 = BsGetUChar(); - - if (magic1 != 'B' || magic2 != 'Z' || magic3 != 'h' || magic4 < '1' || magic4 > '9') - { - streamEnd = true; - return; - } - - SetDecompressStructureSizes(magic4 - '0'); - computedCombinedCRC = 0; - } - - private void InitBlock() - { - char magic1 = BsGetUChar(); - char magic2 = BsGetUChar(); - char magic3 = BsGetUChar(); - char magic4 = BsGetUChar(); - char magic5 = BsGetUChar(); - char magic6 = BsGetUChar(); - - if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) - { - Complete(); - return; - } - - if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) - { - BadBlockHeader(); - streamEnd = true; - return; - } - - storedBlockCRC = BsGetInt32(); - - blockRandomised = (BsR(1) == 1); - - GetAndMoveToFrontDecode(); - - mCrc.Reset(); - currentState = START_BLOCK_STATE; - } - - private void EndBlock() - { - computedBlockCRC = (int)mCrc.Value; - - // -- A bad CRC is considered a fatal error. -- - if (storedBlockCRC != computedBlockCRC) - { - CrcError(); - } - - // 1528150659 - computedCombinedCRC = ((computedCombinedCRC << 1) & 0xFFFFFFFF) | (computedCombinedCRC >> 31); - computedCombinedCRC = computedCombinedCRC ^ (uint)computedBlockCRC; - } - - private void Complete() - { - storedCombinedCRC = BsGetInt32(); - if (storedCombinedCRC != (int)computedCombinedCRC) - { - CrcError(); - } - - streamEnd = true; - } - - private void FillBuffer() - { - int thech = 0; - - try - { - thech = baseStream.ReadByte(); - } - catch (Exception) - { - CompressedStreamEOF(); - } - - if (thech == -1) - { - CompressedStreamEOF(); - } - - bsBuff = (bsBuff << 8) | (thech & 0xFF); - bsLive += 8; - } - - private int BsR(int n) - { - while (bsLive < n) - { - FillBuffer(); - } - - int v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); - bsLive -= n; - return v; - } - - private char BsGetUChar() - { - return (char)BsR(8); - } - - private int BsGetIntVS(int numBits) - { - return BsR(numBits); - } - - private int BsGetInt32() - { - int result = BsR(8); - result = (result << 8) | BsR(8); - result = (result << 8) | BsR(8); - result = (result << 8) | BsR(8); - return result; - } - - private void RecvDecodingTables() - { - char[][] len = new char[BZip2Constants.GroupCount][]; - for (int i = 0; i < BZip2Constants.GroupCount; ++i) - { - len[i] = new char[BZip2Constants.MaximumAlphaSize]; - } - - bool[] inUse16 = new bool[16]; - - //--- Receive the mapping table --- - for (int i = 0; i < 16; i++) - { - inUse16[i] = (BsR(1) == 1); - } - - for (int i = 0; i < 16; i++) - { - if (inUse16[i]) - { - for (int j = 0; j < 16; j++) - { - inUse[i * 16 + j] = (BsR(1) == 1); - } - } - else - { - for (int j = 0; j < 16; j++) - { - inUse[i * 16 + j] = false; - } - } - } - - MakeMaps(); - int alphaSize = nInUse + 2; - - //--- Now the selectors --- - int nGroups = BsR(3); - int nSelectors = BsR(15); - - for (int i = 0; i < nSelectors; i++) - { - int j = 0; - while (BsR(1) == 1) - { - j++; - } - selectorMtf[i] = (byte)j; - } - - //--- Undo the MTF values for the selectors. --- - byte[] pos = new byte[BZip2Constants.GroupCount]; - for (int v = 0; v < nGroups; v++) - { - pos[v] = (byte)v; - } - - for (int i = 0; i < nSelectors; i++) - { - int v = selectorMtf[i]; - byte tmp = pos[v]; - while (v > 0) - { - pos[v] = pos[v - 1]; - v--; - } - pos[0] = tmp; - selector[i] = tmp; - } - - //--- Now the coding tables --- - for (int t = 0; t < nGroups; t++) - { - int curr = BsR(5); - for (int i = 0; i < alphaSize; i++) - { - while (BsR(1) == 1) - { - if (BsR(1) == 0) - { - curr++; - } - else - { - curr--; - } - } - len[t][i] = (char)curr; - } - } - - //--- Create the Huffman decoding tables --- - for (int t = 0; t < nGroups; t++) - { - int minLen = 32; - int maxLen = 0; - for (int i = 0; i < alphaSize; i++) - { - maxLen = Math.Max(maxLen, len[t][i]); - minLen = Math.Min(minLen, len[t][i]); - } - HbCreateDecodeTables(limit[t], baseArray[t], perm[t], len[t], minLen, maxLen, alphaSize); - minLens[t] = minLen; - } - } - - private void GetAndMoveToFrontDecode() - { - byte[] yy = new byte[256]; - int nextSym; - - int limitLast = BZip2Constants.BaseBlockSize * blockSize100k; - origPtr = BsGetIntVS(24); - - RecvDecodingTables(); - int EOB = nInUse + 1; - int groupNo = -1; - int groupPos = 0; - - /*-- - Setting up the unzftab entries here is not strictly - necessary, but it does save having to do it later - in a separate pass, and so saves a block's worth of - cache misses. - --*/ - for (int i = 0; i <= 255; i++) - { - unzftab[i] = 0; - } - - for (int i = 0; i <= 255; i++) - { - yy[i] = (byte)i; - } - - last = -1; - - if (groupPos == 0) - { - groupNo++; - groupPos = BZip2Constants.GroupSize; - } - - groupPos--; - int zt = selector[groupNo]; - int zn = minLens[zt]; - int zvec = BsR(zn); - int zj; - - while (zvec > limit[zt][zn]) - { - if (zn > 20) - { // the longest code - throw new BZip2Exception("Bzip data error"); - } - zn++; - while (bsLive < 1) - { - FillBuffer(); - } - zj = (bsBuff >> (bsLive - 1)) & 1; - bsLive--; - zvec = (zvec << 1) | zj; - } - if (zvec - baseArray[zt][zn] < 0 || zvec - baseArray[zt][zn] >= BZip2Constants.MaximumAlphaSize) - { - throw new BZip2Exception("Bzip data error"); - } - nextSym = perm[zt][zvec - baseArray[zt][zn]]; - - while (true) - { - if (nextSym == EOB) - { - break; - } - - if (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB) - { - int s = -1; - int n = 1; - do - { - if (nextSym == BZip2Constants.RunA) - { - s += (0 + 1) * n; - } - else if (nextSym == BZip2Constants.RunB) - { - s += (1 + 1) * n; - } - - n <<= 1; - - if (groupPos == 0) - { - groupNo++; - groupPos = BZip2Constants.GroupSize; - } - - groupPos--; - - zt = selector[groupNo]; - zn = minLens[zt]; - zvec = BsR(zn); - - while (zvec > limit[zt][zn]) - { - zn++; - while (bsLive < 1) - { - FillBuffer(); - } - zj = (bsBuff >> (bsLive - 1)) & 1; - bsLive--; - zvec = (zvec << 1) | zj; - } - nextSym = perm[zt][zvec - baseArray[zt][zn]]; - } while (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB); - - s++; - byte ch = seqToUnseq[yy[0]]; - unzftab[ch] += s; - - while (s > 0) - { - last++; - ll8[last] = ch; - s--; - } - - if (last >= limitLast) - { - BlockOverrun(); - } - continue; - } - else - { - last++; - if (last >= limitLast) - { - BlockOverrun(); - } - - byte tmp = yy[nextSym - 1]; - unzftab[seqToUnseq[tmp]]++; - ll8[last] = seqToUnseq[tmp]; - - var j = nextSym - 1; + #endregion Constants + + #region Instance Fields + + /*-- + index of the last char in the block, so + the block size == last + 1. + --*/ + private int last; + + /*-- + index in zptr[] of original string after sorting. + --*/ + private int origPtr; + + /*-- + always: in the range 0 .. 9. + The current block size is 100000 * this number. + --*/ + private int blockSize100k; + + private bool blockRandomised; + + private int bsBuff; + private int bsLive; + private readonly IChecksum mCrc = new BZip2Crc(); + + private readonly bool[] inUse = new bool[256]; + private int nInUse; + + private readonly byte[] seqToUnseq = new byte[256]; + private readonly byte[] unseqToSeq = new byte[256]; + + private readonly byte[] selector = new byte[BZip2Constants.MaximumSelectors]; + private readonly byte[] selectorMtf = new byte[BZip2Constants.MaximumSelectors]; + + private int[] tt; + private byte[] ll8; + + /*-- + freq table collected to save a pass over the data + during decompression. + --*/ + private readonly int[] unzftab = new int[256]; + + private readonly int[][] limit = new int[BZip2Constants.GroupCount][]; + private readonly int[][] baseArray = new int[BZip2Constants.GroupCount][]; + private readonly int[][] perm = new int[BZip2Constants.GroupCount][]; + private readonly int[] minLens = new int[BZip2Constants.GroupCount]; + + private readonly Stream baseStream; + private bool streamEnd; + + private int currentChar = -1; + + private int currentState = START_BLOCK_STATE; + + private int storedBlockCRC, storedCombinedCRC; + private int computedBlockCRC; + private uint computedCombinedCRC; + + private int count, chPrev, ch2; + private int tPos; + private int rNToGo; + private int rTPos; + private int i2, j2; + private byte z; + + #endregion Instance Fields + + /// + /// Construct instance for reading from stream + /// + /// Data source + public BZip2InputStream(Stream stream) + { + // init arrays + for (var i = 0; i < BZip2Constants.GroupCount; ++i) + { + limit[i] = new int[BZip2Constants.MaximumAlphaSize]; + baseArray[i] = new int[BZip2Constants.MaximumAlphaSize]; + perm[i] = new int[BZip2Constants.MaximumAlphaSize]; + } + + baseStream = stream ?? throw new ArgumentNullException(nameof(stream)); + bsLive = 0; + bsBuff = 0; + Initialize(); + InitBlock(); + SetupBlock(); + } + + /// + /// Get/set flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + public bool IsStreamOwner { get; set; } = true; + + #region Stream Overrides + + /// + /// Gets a value indicating if the stream supports reading + /// + public override bool CanRead + { + get + { + return baseStream.CanRead; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// This property always returns false + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get + { + return baseStream.Length; + } + } + + /// + /// Gets the current position of the stream. + /// Setting the position is not supported and will throw a NotSupportException. + /// + /// Any attempt to set the position. + public override long Position + { + get + { + return baseStream.Position; + } + set + { + throw new NotSupportedException("BZip2InputStream position cannot be set"); + } + } + + /// + /// Flushes the stream. + /// + public override void Flush() + { + baseStream.Flush(); + } + + /// + /// Set the streams position. This operation is not supported and will throw a NotSupportedException + /// + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// The new position of the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("BZip2InputStream Seek not supported"); + } + + /// + /// Sets the length of this stream to the given value. + /// This operation is not supported and will throw a NotSupportedExceptionortedException + /// + /// The new length for the stream. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("BZip2InputStream SetLength not supported"); + } + + /// + /// Writes a block of bytes to this stream using data from a buffer. + /// This operation is not supported and will throw a NotSupportedException + /// + /// The buffer to source data from. + /// The offset to start obtaining data from. + /// The number of bytes of data to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("BZip2InputStream Write not supported"); + } + + /// + /// Writes a byte to the current position in the file stream. + /// This operation is not supported and will throw a NotSupportedException + /// + /// The value to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("BZip2InputStream WriteByte not supported"); + } + + /// + /// Read a sequence of bytes and advances the read position by one byte. + /// + /// Array of bytes to store values in + /// Offset in array to begin storing data + /// The maximum number of bytes to read + /// The total number of bytes read into the buffer. This might be less + /// than the number of bytes requested if that number of bytes are not + /// currently available or zero if the end of the stream is reached. + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + for (var i = 0; i < count; ++i) + { + var rb = ReadByte(); + if (rb == -1) + { + return i; + } + buffer[offset + i] = (byte)rb; + } + return count; + } + + /// + /// Closes the stream, releasing any associated resources. + /// + protected override void Dispose(bool disposing) + { + if (disposing && IsStreamOwner) + { + baseStream.Dispose(); + } + } + + /// + /// Read a byte from stream advancing position + /// + /// byte read or -1 on end of stream + public override int ReadByte() + { + if (streamEnd) + { + return -1; // ok + } + + var retChar = currentChar; + switch (currentState) + { + case RAND_PART_B_STATE: + SetupRandPartB(); + break; + + case RAND_PART_C_STATE: + SetupRandPartC(); + break; + + case NO_RAND_PART_B_STATE: + SetupNoRandPartB(); + break; + + case NO_RAND_PART_C_STATE: + SetupNoRandPartC(); + break; + + case START_BLOCK_STATE: + case NO_RAND_PART_A_STATE: + case RAND_PART_A_STATE: + break; + } + return retChar; + } + + #endregion Stream Overrides + + private void MakeMaps() + { + nInUse = 0; + for (var i = 0; i < 256; ++i) + { + if (inUse[i]) + { + seqToUnseq[nInUse] = (byte)i; + unseqToSeq[i] = (byte)nInUse; + nInUse++; + } + } + } + + private void Initialize() + { + var magic1 = BsGetUChar(); + var magic2 = BsGetUChar(); + + var magic3 = BsGetUChar(); + var magic4 = BsGetUChar(); + + if (magic1 != 'B' || magic2 != 'Z' || magic3 != 'h' || magic4 < '1' || magic4 > '9') + { + streamEnd = true; + return; + } + + SetDecompressStructureSizes(magic4 - '0'); + computedCombinedCRC = 0; + } + + private void InitBlock() + { + var magic1 = BsGetUChar(); + var magic2 = BsGetUChar(); + var magic3 = BsGetUChar(); + var magic4 = BsGetUChar(); + var magic5 = BsGetUChar(); + var magic6 = BsGetUChar(); + + if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) + { + Complete(); + return; + } + + if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) + { + BadBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = BsGetInt32(); + + blockRandomised = BsR(1) == 1; + + GetAndMoveToFrontDecode(); + + mCrc.Reset(); + currentState = START_BLOCK_STATE; + } + + private void EndBlock() + { + computedBlockCRC = (int)mCrc.Value; + + // -- A bad CRC is considered a fatal error. -- + if (storedBlockCRC != computedBlockCRC) + { + CrcError(); + } + + // 1528150659 + computedCombinedCRC = ((computedCombinedCRC << 1) & 0xFFFFFFFF) | (computedCombinedCRC >> 31); + computedCombinedCRC ^= (uint)computedBlockCRC; + } + + private void Complete() + { + storedCombinedCRC = BsGetInt32(); + if (storedCombinedCRC != (int)computedCombinedCRC) + { + CrcError(); + } + + streamEnd = true; + } + + private void FillBuffer() + { + var thech = 0; + + try + { + thech = baseStream.ReadByte(); + } + catch (Exception) + { + CompressedStreamEOF(); + } + + if (thech == -1) + { + CompressedStreamEOF(); + } + + bsBuff = (bsBuff << 8) | (thech & 0xFF); + bsLive += 8; + } + + private int BsR(int n) + { + while (bsLive < n) + { + FillBuffer(); + } + + var v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); + bsLive -= n; + return v; + } + + private char BsGetUChar() + { + return (char)BsR(8); + } + + private int BsGetIntVS(int numBits) + { + return BsR(numBits); + } + + private int BsGetInt32() + { + var result = BsR(8); + result = (result << 8) | BsR(8); + result = (result << 8) | BsR(8); + result = (result << 8) | BsR(8); + return result; + } + + private void RecvDecodingTables() + { + var len = new char[BZip2Constants.GroupCount][]; + for (var i = 0; i < BZip2Constants.GroupCount; ++i) + { + len[i] = new char[BZip2Constants.MaximumAlphaSize]; + } + + var inUse16 = new bool[16]; + + //--- Receive the mapping table --- + for (var i = 0; i < 16; i++) + { + inUse16[i] = BsR(1) == 1; + } + + for (var i = 0; i < 16; i++) + { + if (inUse16[i]) + { + for (var j = 0; j < 16; j++) + { + inUse[(i * 16) + j] = BsR(1) == 1; + } + } + else + { + for (var j = 0; j < 16; j++) + { + inUse[(i * 16) + j] = false; + } + } + } + + MakeMaps(); + var alphaSize = nInUse + 2; + + //--- Now the selectors --- + var nGroups = BsR(3); + var nSelectors = BsR(15); + + for (var i = 0; i < nSelectors; i++) + { + var j = 0; + while (BsR(1) == 1) + { + j++; + } + selectorMtf[i] = (byte)j; + } + + //--- Undo the MTF values for the selectors. --- + var pos = new byte[BZip2Constants.GroupCount]; + for (var v = 0; v < nGroups; v++) + { + pos[v] = (byte)v; + } + + for (var i = 0; i < nSelectors; i++) + { + int v = selectorMtf[i]; + var tmp = pos[v]; + while (v > 0) + { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + + //--- Now the coding tables --- + for (var t = 0; t < nGroups; t++) + { + var curr = BsR(5); + for (var i = 0; i < alphaSize; i++) + { + while (BsR(1) == 1) + { + if (BsR(1) == 0) + { + curr++; + } + else + { + curr--; + } + } + len[t][i] = (char)curr; + } + } + + //--- Create the Huffman decoding tables --- + for (var t = 0; t < nGroups; t++) + { + var minLen = 32; + var maxLen = 0; + for (var i = 0; i < alphaSize; i++) + { + maxLen = Math.Max(maxLen, len[t][i]); + minLen = Math.Min(minLen, len[t][i]); + } + HbCreateDecodeTables(limit[t], baseArray[t], perm[t], len[t], minLen, maxLen, alphaSize); + minLens[t] = minLen; + } + } + + private void GetAndMoveToFrontDecode() + { + var yy = new byte[256]; + int nextSym; + + var limitLast = BZip2Constants.BaseBlockSize * blockSize100k; + origPtr = BsGetIntVS(24); + + RecvDecodingTables(); + var EOB = nInUse + 1; + var groupNo = -1; + var groupPos = 0; + + /*-- + Setting up the unzftab entries here is not strictly + necessary, but it does save having to do it later + in a separate pass, and so saves a block's worth of + cache misses. + --*/ + for (var i = 0; i <= 255; i++) + { + unzftab[i] = 0; + } + + for (var i = 0; i <= 255; i++) + { + yy[i] = (byte)i; + } + + last = -1; + + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + int zt = selector[groupNo]; + var zn = minLens[zt]; + var zvec = BsR(zn); + int zj; + + while (zvec > limit[zt][zn]) + { + if (zn > 20) + { // the longest code + throw new BZip2Exception("Bzip data error"); + } + zn++; + while (bsLive < 1) + { + FillBuffer(); + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + if (zvec - baseArray[zt][zn] is < 0 or >= BZip2Constants.MaximumAlphaSize) + { + throw new BZip2Exception("Bzip data error"); + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + + while (true) + { + if (nextSym == EOB) + { + break; + } + + if (nextSym is BZip2Constants.RunA or BZip2Constants.RunB) + { + var s = -1; + var n = 1; + do + { + if (nextSym == BZip2Constants.RunA) + { + s += (0 + 1) * n; + } + else if (nextSym == BZip2Constants.RunB) + { + s += (1 + 1) * n; + } + + n <<= 1; + + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + + while (zvec > limit[zt][zn]) + { + zn++; + while (bsLive < 1) + { + FillBuffer(); + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + } while (nextSym is BZip2Constants.RunA or BZip2Constants.RunB); + + s++; + var ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while (s > 0) + { + last++; + ll8[last] = ch; + s--; + } + + if (last >= limitLast) + { + BlockOverrun(); + } + continue; + } + else + { + last++; + if (last >= limitLast) + { + BlockOverrun(); + } + + var tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + var j = nextSym - 1; #if VECTORIZE_MEMORY_MOVE // This is vectorized memory move. Going from the back, we're taking chunks of array @@ -735,319 +733,316 @@ cache misses. } #endif // VECTORIZE_MEMORY_MOVE - while(j > 0) - { - yy[j] = yy[--j]; - } - - yy[0] = tmp; - - if (groupPos == 0) - { - groupNo++; - groupPos = BZip2Constants.GroupSize; - } - - groupPos--; - zt = selector[groupNo]; - zn = minLens[zt]; - zvec = BsR(zn); - while (zvec > limit[zt][zn]) - { - zn++; - while (bsLive < 1) - { - FillBuffer(); - } - zj = (bsBuff >> (bsLive - 1)) & 1; - bsLive--; - zvec = (zvec << 1) | zj; - } - nextSym = perm[zt][zvec - baseArray[zt][zn]]; - continue; - } - } - } - - private void SetupBlock() - { - int[] cftab = new int[257]; - - cftab[0] = 0; - Array.Copy(unzftab, 0, cftab, 1, 256); - - for (int i = 1; i <= 256; i++) - { - cftab[i] += cftab[i - 1]; - } - - for (int i = 0; i <= last; i++) - { - byte ch = ll8[i]; - tt[cftab[ch]] = i; - cftab[ch]++; - } - - cftab = null; - - tPos = tt[origPtr]; - - count = 0; - i2 = 0; - ch2 = 256; /*-- not a char and not EOF --*/ - - if (blockRandomised) - { - rNToGo = 0; - rTPos = 0; - SetupRandPartA(); - } - else - { - SetupNoRandPartA(); - } - } - - private void SetupRandPartA() - { - if (i2 <= last) - { - chPrev = ch2; - ch2 = ll8[tPos]; - tPos = tt[tPos]; - if (rNToGo == 0) - { - rNToGo = BZip2Constants.RandomNumbers[rTPos]; - rTPos++; - if (rTPos == 512) - { - rTPos = 0; - } - } - rNToGo--; - ch2 ^= (int)((rNToGo == 1) ? 1 : 0); - i2++; - - currentChar = ch2; - currentState = RAND_PART_B_STATE; - mCrc.Update(ch2); - } - else - { - EndBlock(); - InitBlock(); - SetupBlock(); - } - } - - private void SetupNoRandPartA() - { - if (i2 <= last) - { - chPrev = ch2; - ch2 = ll8[tPos]; - tPos = tt[tPos]; - i2++; - - currentChar = ch2; - currentState = NO_RAND_PART_B_STATE; - mCrc.Update(ch2); - } - else - { - EndBlock(); - InitBlock(); - SetupBlock(); - } - } - - private void SetupRandPartB() - { - if (ch2 != chPrev) - { - currentState = RAND_PART_A_STATE; - count = 1; - SetupRandPartA(); - } - else - { - count++; - if (count >= 4) - { - z = ll8[tPos]; - tPos = tt[tPos]; - if (rNToGo == 0) - { - rNToGo = BZip2Constants.RandomNumbers[rTPos]; - rTPos++; - if (rTPos == 512) - { - rTPos = 0; - } - } - rNToGo--; - z ^= (byte)((rNToGo == 1) ? 1 : 0); - j2 = 0; - currentState = RAND_PART_C_STATE; - SetupRandPartC(); - } - else - { - currentState = RAND_PART_A_STATE; - SetupRandPartA(); - } - } - } - - private void SetupRandPartC() - { - if (j2 < (int)z) - { - currentChar = ch2; - mCrc.Update(ch2); - j2++; - } - else - { - currentState = RAND_PART_A_STATE; - i2++; - count = 0; - SetupRandPartA(); - } - } - - private void SetupNoRandPartB() - { - if (ch2 != chPrev) - { - currentState = NO_RAND_PART_A_STATE; - count = 1; - SetupNoRandPartA(); - } - else - { - count++; - if (count >= 4) - { - z = ll8[tPos]; - tPos = tt[tPos]; - currentState = NO_RAND_PART_C_STATE; - j2 = 0; - SetupNoRandPartC(); - } - else - { - currentState = NO_RAND_PART_A_STATE; - SetupNoRandPartA(); - } - } - } - - private void SetupNoRandPartC() - { - if (j2 < (int)z) - { - currentChar = ch2; - mCrc.Update(ch2); - j2++; - } - else - { - currentState = NO_RAND_PART_A_STATE; - i2++; - count = 0; - SetupNoRandPartA(); - } - } - - private void SetDecompressStructureSizes(int newSize100k) - { - if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k && blockSize100k <= 9)) - { - throw new BZip2Exception("Invalid block size"); - } - - blockSize100k = newSize100k; - - if (newSize100k == 0) - { - return; - } - - int n = BZip2Constants.BaseBlockSize * newSize100k; - ll8 = new byte[n]; - tt = new int[n]; - } - - private static void CompressedStreamEOF() - { - throw new EndOfStreamException("BZip2 input stream end of compressed stream"); - } - - private static void BlockOverrun() - { - throw new BZip2Exception("BZip2 input stream block overrun"); - } - - private static void BadBlockHeader() - { - throw new BZip2Exception("BZip2 input stream bad block header"); - } - - private static void CrcError() - { - throw new BZip2Exception("BZip2 input stream crc error"); - } - - private static void HbCreateDecodeTables(int[] limit, int[] baseArray, int[] perm, char[] length, int minLen, int maxLen, int alphaSize) - { - int pp = 0; - - for (int i = minLen; i <= maxLen; ++i) - { - for (int j = 0; j < alphaSize; ++j) - { - if (length[j] == i) - { - perm[pp] = j; - ++pp; - } - } - } - - for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) - { - baseArray[i] = 0; - } - - for (int i = 0; i < alphaSize; i++) - { - ++baseArray[length[i] + 1]; - } - - for (int i = 1; i < BZip2Constants.MaximumCodeLength; i++) - { - baseArray[i] += baseArray[i - 1]; - } - - for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) - { - limit[i] = 0; - } - - int vec = 0; - - for (int i = minLen; i <= maxLen; i++) - { - vec += (baseArray[i + 1] - baseArray[i]); - limit[i] = vec - 1; - vec <<= 1; - } - - for (int i = minLen + 1; i <= maxLen; i++) - { - baseArray[i] = ((limit[i - 1] + 1) << 1) - baseArray[i]; - } - } - } + while (j > 0) + { + yy[j] = yy[--j]; + } + + yy[0] = tmp; + + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) + { + zn++; + while (bsLive < 1) + { + FillBuffer(); + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + continue; + } + } + } + + private void SetupBlock() + { + var cftab = new int[257]; + + cftab[0] = 0; + Array.Copy(unzftab, 0, cftab, 1, 256); + + for (var i = 1; i <= 256; i++) + { + cftab[i] += cftab[i - 1]; + } + + for (var i = 0; i <= last; i++) + { + var ch = ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; /*-- not a char and not EOF --*/ + + if (blockRandomised) + { + rNToGo = 0; + rTPos = 0; + SetupRandPartA(); + } + else + { + SetupNoRandPartA(); + } + } + + private void SetupRandPartA() + { + if (i2 <= last) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) + { + rNToGo = BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + ch2 ^= (rNToGo == 1) ? 1 : 0; + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.Update(ch2); + } + else + { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupNoRandPartA() + { + if (i2 <= last) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.Update(ch2); + } + else + { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupRandPartB() + { + if (ch2 != chPrev) + { + currentState = RAND_PART_A_STATE; + count = 1; + SetupRandPartA(); + } + else + { + count++; + if (count >= 4) + { + z = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) + { + rNToGo = BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + z ^= (byte)((rNToGo == 1) ? 1 : 0); + j2 = 0; + currentState = RAND_PART_C_STATE; + SetupRandPartC(); + } + else + { + currentState = RAND_PART_A_STATE; + SetupRandPartA(); + } + } + } + + private void SetupRandPartC() + { + if (j2 < z) + { + currentChar = ch2; + mCrc.Update(ch2); + j2++; + } + else + { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + SetupRandPartA(); + } + } + + private void SetupNoRandPartB() + { + if (ch2 != chPrev) + { + currentState = NO_RAND_PART_A_STATE; + count = 1; + SetupNoRandPartA(); + } + else + { + count++; + if (count >= 4) + { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + SetupNoRandPartC(); + } + else + { + currentState = NO_RAND_PART_A_STATE; + SetupNoRandPartA(); + } + } + } + + private void SetupNoRandPartC() + { + if (j2 < z) + { + currentChar = ch2; + mCrc.Update(ch2); + j2++; + } + else + { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + SetupNoRandPartA(); + } + } + + private void SetDecompressStructureSizes(int newSize100k) + { + if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k && blockSize100k <= 9)) + { + throw new BZip2Exception("Invalid block size"); + } + + blockSize100k = newSize100k; + + if (newSize100k == 0) + { + return; + } + + var n = BZip2Constants.BaseBlockSize * newSize100k; + ll8 = new byte[n]; + tt = new int[n]; + } + + private static void CompressedStreamEOF() + { + throw new EndOfStreamException("BZip2 input stream end of compressed stream"); + } + + private static void BlockOverrun() + { + throw new BZip2Exception("BZip2 input stream block overrun"); + } + + private static void BadBlockHeader() + { + throw new BZip2Exception("BZip2 input stream bad block header"); + } + + private static void CrcError() + { + throw new BZip2Exception("BZip2 input stream crc error"); + } + + private static void HbCreateDecodeTables(int[] limit, int[] baseArray, int[] perm, char[] length, int minLen, int maxLen, int alphaSize) + { + var pp = 0; + + for (var i = minLen; i <= maxLen; ++i) + { + for (var j = 0; j < alphaSize; ++j) + { + if (length[j] == i) + { + perm[pp] = j; + ++pp; + } + } + } + + for (var i = 0; i < BZip2Constants.MaximumCodeLength; i++) + { + baseArray[i] = 0; + } + + for (var i = 0; i < alphaSize; i++) + { + ++baseArray[length[i] + 1]; + } + + for (var i = 1; i < BZip2Constants.MaximumCodeLength; i++) + { + baseArray[i] += baseArray[i - 1]; + } + + for (var i = 0; i < BZip2Constants.MaximumCodeLength; i++) + { + limit[i] = 0; + } + + var vec = 0; + + for (var i = minLen; i <= maxLen; i++) + { + vec += baseArray[i + 1] - baseArray[i]; + limit[i] = vec - 1; + vec <<= 1; + } + + for (var i = minLen + 1; i <= maxLen; i++) + { + baseArray[i] = ((limit[i - 1] + 1) << 1) - baseArray[i]; + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs index 01da6a549..6921f9927 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs @@ -2,2032 +2,2013 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2 +namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2; + +/// +/// An output stream that compresses into the BZip2 format +/// including file header chars into another stream. +/// +public class BZip2OutputStream : Stream { - /// - /// An output stream that compresses into the BZip2 format - /// including file header chars into another stream. - /// - public class BZip2OutputStream : Stream - { - #region Constants - - private const int SETMASK = (1 << 21); - private const int CLEARMASK = (~SETMASK); - private const int GREATER_ICOST = 15; - private const int LESSER_ICOST = 0; - private const int SMALL_THRESH = 20; - private const int DEPTH_THRESH = 10; - - /*-- - If you are ever unlucky/improbable enough - to get a stack overflow whilst sorting, - increase the following constant and try - again. In practice I have never seen the - stack go above 27 elems, so the following - limit seems very generous. - --*/ - private const int QSORT_STACK_SIZE = 1000; - - /*-- - Knuth's increments seem to work better - than Incerpi-Sedgewick here. Possibly - because the number of elems to sort is - usually small, typically <= 20. - --*/ - - private readonly int[] increments = { - 1, 4, 13, 40, 121, 364, 1093, 3280, - 9841, 29524, 88573, 265720, - 797161, 2391484 - }; - - #endregion Constants - - #region Instance Fields - - /*-- - index of the last char in the block, so - the block size == last + 1. - --*/ - private int last; - - /*-- - index in zptr[] of original string after sorting. - --*/ - private int origPtr; - - /*-- - always: in the range 0 .. 9. - The current block size is 100000 * this number. - --*/ - private int blockSize100k; - - private bool blockRandomised; - - private int bytesOut; - private int bsBuff; - private int bsLive; - private IChecksum mCrc = new BZip2Crc(); - - private bool[] inUse = new bool[256]; - private int nInUse; - - private char[] seqToUnseq = new char[256]; - private char[] unseqToSeq = new char[256]; - - private char[] selector = new char[BZip2Constants.MaximumSelectors]; - private char[] selectorMtf = new char[BZip2Constants.MaximumSelectors]; - - private byte[] block; - private int[] quadrant; - private int[] zptr; - private short[] szptr; - private int[] ftab; - - private int nMTF; - - private int[] mtfFreq = new int[BZip2Constants.MaximumAlphaSize]; - - /* - * Used when sorting. If too many long comparisons - * happen, we stop sorting, randomise the block - * slightly, and try again. - */ - private int workFactor; - private int workDone; - private int workLimit; - private bool firstAttempt; - private int nBlocksRandomised; - - private int currentChar = -1; - private int runLength; - private uint blockCRC, combinedCRC; - private int allowableBlockSize; - private readonly Stream baseStream; - private bool disposed_; - - #endregion Instance Fields - - /// - /// Construct a default output stream with maximum block size - /// - /// The stream to write BZip data onto. - public BZip2OutputStream(Stream stream) : this(stream, 9) - { - } - - /// - /// Initialise a new instance of the - /// for the specified stream, using the given blocksize. - /// - /// The stream to write compressed data to. - /// The block size to use. - /// - /// Valid block sizes are in the range 1..9, with 1 giving - /// the lowest compression and 9 the highest. - /// - public BZip2OutputStream(Stream stream, int blockSize) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - baseStream = stream; - bsLive = 0; - bsBuff = 0; - bytesOut = 0; - - workFactor = 50; - if (blockSize > 9) - { - blockSize = 9; - } - - if (blockSize < 1) - { - blockSize = 1; - } - blockSize100k = blockSize; - AllocateCompressStructures(); - Initialize(); - InitBlock(); - } - - /// - /// Ensures that resources are freed and other cleanup operations - /// are performed when the garbage collector reclaims the BZip2OutputStream. - /// - ~BZip2OutputStream() - { - Dispose(false); - } - - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner { get; set; } = true; - - /// - /// Gets a value indicating whether the current stream supports reading - /// - public override bool CanRead - { - get - { - return false; - } - } - - /// - /// Gets a value indicating whether the current stream supports seeking - /// - public override bool CanSeek - { - get - { - return false; - } - } - - /// - /// Gets a value indicating whether the current stream supports writing - /// - public override bool CanWrite - { - get - { - return baseStream.CanWrite; - } - } - - /// - /// Gets the length in bytes of the stream - /// - public override long Length - { - get - { - return baseStream.Length; - } - } - - /// - /// Gets or sets the current position of this stream. - /// - public override long Position - { - get - { - return baseStream.Position; - } - set - { - throw new NotSupportedException("BZip2OutputStream position cannot be set"); - } - } - - /// - /// Sets the current position of this stream to the given value. - /// - /// The point relative to the offset from which to being seeking. - /// The reference point from which to begin seeking. - /// The new position in the stream. - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("BZip2OutputStream Seek not supported"); - } - - /// - /// Sets the length of this stream to the given value. - /// - /// The new stream length. - public override void SetLength(long value) - { - throw new NotSupportedException("BZip2OutputStream SetLength not supported"); - } - - /// - /// Read a byte from the stream advancing the position. - /// - /// The byte read cast to an int; -1 if end of stream. - public override int ReadByte() - { - throw new NotSupportedException("BZip2OutputStream ReadByte not supported"); - } - - /// - /// Read a block of bytes - /// - /// The buffer to read into. - /// The offset in the buffer to start storing data at. - /// The maximum number of bytes to read. - /// The total number of bytes read. This might be less than the number of bytes - /// requested if that number of bytes are not currently available, or zero - /// if the end of the stream is reached. - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("BZip2OutputStream Read not supported"); - } - - /// - /// Write a block of bytes to the stream - /// - /// The buffer containing data to write. - /// The offset of the first byte to write. - /// The number of bytes to write. - public override void Write(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - if (buffer.Length - offset < count) - { - throw new ArgumentException("Offset/count out of range"); - } - - for (int i = 0; i < count; ++i) - { - WriteByte(buffer[offset + i]); - } - } - - /// - /// Write a byte to the stream. - /// - /// The byte to write to the stream. - public override void WriteByte(byte value) - { - int b = (256 + value) % 256; - if (currentChar != -1) - { - if (currentChar == b) - { - runLength++; - if (runLength > 254) - { - WriteRun(); - currentChar = -1; - runLength = 0; - } - } - else - { - WriteRun(); - runLength = 1; - currentChar = b; - } - } - else - { - currentChar = b; - runLength++; - } - } - - private void MakeMaps() - { - nInUse = 0; - for (int i = 0; i < 256; i++) - { - if (inUse[i]) - { - seqToUnseq[nInUse] = (char)i; - unseqToSeq[i] = (char)nInUse; - nInUse++; - } - } - } - - /// - /// Get the number of bytes written to output. - /// - private void WriteRun() - { - if (last < allowableBlockSize) - { - inUse[currentChar] = true; - for (int i = 0; i < runLength; i++) - { - mCrc.Update(currentChar); - } - - switch (runLength) - { - case 1: - last++; - block[last + 1] = (byte)currentChar; - break; - - case 2: - last++; - block[last + 1] = (byte)currentChar; - last++; - block[last + 1] = (byte)currentChar; - break; - - case 3: - last++; - block[last + 1] = (byte)currentChar; - last++; - block[last + 1] = (byte)currentChar; - last++; - block[last + 1] = (byte)currentChar; - break; - - default: - inUse[runLength - 4] = true; - last++; - block[last + 1] = (byte)currentChar; - last++; - block[last + 1] = (byte)currentChar; - last++; - block[last + 1] = (byte)currentChar; - last++; - block[last + 1] = (byte)currentChar; - last++; - block[last + 1] = (byte)(runLength - 4); - break; - } - } - else - { - EndBlock(); - InitBlock(); - WriteRun(); - } - } - - /// - /// Get the number of bytes written to the output. - /// - public int BytesWritten - { - get { return bytesOut; } - } - - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - override protected void Dispose(bool disposing) - { - try - { - try - { - base.Dispose(disposing); - if (!disposed_) - { - disposed_ = true; - - if (runLength > 0) - { - WriteRun(); - } - - currentChar = -1; - EndBlock(); - EndCompression(); - Flush(); - } - } - finally - { - if (disposing) - { - if (IsStreamOwner) - { - baseStream.Dispose(); - } - } - } - } - catch - { - } - } - - /// - /// Flush output buffers - /// - public override void Flush() - { - baseStream.Flush(); - } - - private void Initialize() - { - bytesOut = 0; - nBlocksRandomised = 0; - - /*--- Write header `magic' bytes indicating file-format == huffmanised, - followed by a digit indicating blockSize100k. - ---*/ - - BsPutUChar('B'); - BsPutUChar('Z'); - - BsPutUChar('h'); - BsPutUChar('0' + blockSize100k); - - combinedCRC = 0; - } - - private void InitBlock() - { - mCrc.Reset(); - last = -1; - - for (int i = 0; i < 256; i++) - { - inUse[i] = false; - } - - /*--- 20 is just a paranoia constant ---*/ - allowableBlockSize = BZip2Constants.BaseBlockSize * blockSize100k - 20; - } - - private void EndBlock() - { - if (last < 0) - { // dont do anything for empty files, (makes empty files compatible with original Bzip) - return; - } - - blockCRC = unchecked((uint)mCrc.Value); - combinedCRC = (combinedCRC << 1) | (combinedCRC >> 31); - combinedCRC ^= blockCRC; - - /*-- sort the block and establish position of original string --*/ - DoReversibleTransformation(); - - /*-- - A 6-byte block header, the value chosen arbitrarily - as 0x314159265359 :-). A 32 bit value does not really - give a strong enough guarantee that the value will not - appear by chance in the compressed datastream. Worst-case - probability of this event, for a 900k block, is about - 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. - For a compressed file of size 100Gb -- about 100000 blocks -- - only a 48-bit marker will do. NB: normal compression/ - decompression do *not* rely on these statistical properties. - They are only important when trying to recover blocks from - damaged files. - --*/ - BsPutUChar(0x31); - BsPutUChar(0x41); - BsPutUChar(0x59); - BsPutUChar(0x26); - BsPutUChar(0x53); - BsPutUChar(0x59); - - /*-- Now the block's CRC, so it is in a known place. --*/ - unchecked - { - BsPutint((int)blockCRC); - } - - /*-- Now a single bit indicating randomisation. --*/ - if (blockRandomised) - { - BsW(1, 1); - nBlocksRandomised++; - } - else - { - BsW(1, 0); - } - - /*-- Finally, block's contents proper. --*/ - MoveToFrontCodeAndSend(); - } - - private void EndCompression() - { - /*-- - Now another magic 48-bit number, 0x177245385090, to - indicate the end of the last block. (sqrt(pi), if - you want to know. I did want to use e, but it contains - too much repetition -- 27 18 28 18 28 46 -- for me - to feel statistically comfortable. Call me paranoid.) - --*/ - BsPutUChar(0x17); - BsPutUChar(0x72); - BsPutUChar(0x45); - BsPutUChar(0x38); - BsPutUChar(0x50); - BsPutUChar(0x90); - - unchecked - { - BsPutint((int)combinedCRC); - } - - BsFinishedWithStream(); - } - - private void BsFinishedWithStream() - { - while (bsLive > 0) - { - int ch = (bsBuff >> 24); - baseStream.WriteByte((byte)ch); // write 8-bit - bsBuff <<= 8; - bsLive -= 8; - bytesOut++; - } - } - - private void BsW(int n, int v) - { - while (bsLive >= 8) - { - int ch = (bsBuff >> 24); - unchecked { baseStream.WriteByte((byte)ch); } // write 8-bit - bsBuff <<= 8; - bsLive -= 8; - ++bytesOut; - } - bsBuff |= (v << (32 - bsLive - n)); - bsLive += n; - } - - private void BsPutUChar(int c) - { - BsW(8, c); - } - - private void BsPutint(int u) - { - BsW(8, (u >> 24) & 0xFF); - BsW(8, (u >> 16) & 0xFF); - BsW(8, (u >> 8) & 0xFF); - BsW(8, u & 0xFF); - } - - private void BsPutIntVS(int numBits, int c) - { - BsW(numBits, c); - } - - private void SendMTFValues() - { - char[][] len = new char[BZip2Constants.GroupCount][]; - for (int i = 0; i < BZip2Constants.GroupCount; ++i) - { - len[i] = new char[BZip2Constants.MaximumAlphaSize]; - } - - int gs, ge, totc, bt, bc, iter; - int nSelectors = 0, alphaSize, minLen, maxLen, selCtr; - int nGroups; - - alphaSize = nInUse + 2; - for (int t = 0; t < BZip2Constants.GroupCount; t++) - { - for (int v = 0; v < alphaSize; v++) - { - len[t][v] = (char)GREATER_ICOST; - } - } - - /*--- Decide how many coding tables to use ---*/ - if (nMTF <= 0) - { - Panic(); - } - - if (nMTF < 200) - { - nGroups = 2; - } - else if (nMTF < 600) - { - nGroups = 3; - } - else if (nMTF < 1200) - { - nGroups = 4; - } - else if (nMTF < 2400) - { - nGroups = 5; - } - else - { - nGroups = 6; - } - - /*--- Generate an initial set of coding tables ---*/ - int nPart = nGroups; - int remF = nMTF; - gs = 0; - while (nPart > 0) - { - int tFreq = remF / nPart; - int aFreq = 0; - ge = gs - 1; - while (aFreq < tFreq && ge < alphaSize - 1) - { - ge++; - aFreq += mtfFreq[ge]; - } - - if (ge > gs && nPart != nGroups && nPart != 1 && ((nGroups - nPart) % 2 == 1)) - { - aFreq -= mtfFreq[ge]; - ge--; - } - - for (int v = 0; v < alphaSize; v++) - { - if (v >= gs && v <= ge) - { - len[nPart - 1][v] = (char)LESSER_ICOST; - } - else - { - len[nPart - 1][v] = (char)GREATER_ICOST; - } - } - - nPart--; - gs = ge + 1; - remF -= aFreq; - } - - int[][] rfreq = new int[BZip2Constants.GroupCount][]; - for (int i = 0; i < BZip2Constants.GroupCount; ++i) - { - rfreq[i] = new int[BZip2Constants.MaximumAlphaSize]; - } - - int[] fave = new int[BZip2Constants.GroupCount]; - short[] cost = new short[BZip2Constants.GroupCount]; - /*--- - Iterate up to N_ITERS times to improve the tables. - ---*/ - for (iter = 0; iter < BZip2Constants.NumberOfIterations; ++iter) - { - for (int t = 0; t < nGroups; ++t) - { - fave[t] = 0; - } - - for (int t = 0; t < nGroups; ++t) - { - for (int v = 0; v < alphaSize; ++v) - { - rfreq[t][v] = 0; - } - } - - nSelectors = 0; - totc = 0; - gs = 0; - while (true) - { - /*--- Set group start & end marks. --*/ - if (gs >= nMTF) - { - break; - } - ge = gs + BZip2Constants.GroupSize - 1; - if (ge >= nMTF) - { - ge = nMTF - 1; - } - - /*-- - Calculate the cost of this group as coded - by each of the coding tables. - --*/ - for (int t = 0; t < nGroups; t++) - { - cost[t] = 0; - } - - if (nGroups == 6) - { - short cost0, cost1, cost2, cost3, cost4, cost5; - cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; - for (int i = gs; i <= ge; ++i) - { - short icv = szptr[i]; - cost0 += (short)len[0][icv]; - cost1 += (short)len[1][icv]; - cost2 += (short)len[2][icv]; - cost3 += (short)len[3][icv]; - cost4 += (short)len[4][icv]; - cost5 += (short)len[5][icv]; - } - cost[0] = cost0; - cost[1] = cost1; - cost[2] = cost2; - cost[3] = cost3; - cost[4] = cost4; - cost[5] = cost5; - } - else - { - for (int i = gs; i <= ge; ++i) - { - short icv = szptr[i]; - for (int t = 0; t < nGroups; t++) - { - cost[t] += (short)len[t][icv]; - } - } - } - - /*-- - Find the coding table which is best for this group, - and record its identity in the selector table. - --*/ - bc = 999999999; - bt = -1; - for (int t = 0; t < nGroups; ++t) - { - if (cost[t] < bc) - { - bc = cost[t]; - bt = t; - } - } - totc += bc; - fave[bt]++; - selector[nSelectors] = (char)bt; - nSelectors++; - - /*-- - Increment the symbol frequencies for the selected table. - --*/ - for (int i = gs; i <= ge; ++i) - { - ++rfreq[bt][szptr[i]]; - } - - gs = ge + 1; - } - - /*-- - Recompute the tables based on the accumulated frequencies. - --*/ - for (int t = 0; t < nGroups; ++t) - { - HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); - } - } - - rfreq = null; - fave = null; - cost = null; - - if (!(nGroups < 8)) - { - Panic(); - } - - if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZip2Constants.GroupSize)))) - { - Panic(); - } - - /*--- Compute MTF values for the selectors. ---*/ - char[] pos = new char[BZip2Constants.GroupCount]; - char ll_i, tmp2, tmp; - - for (int i = 0; i < nGroups; i++) - { - pos[i] = (char)i; - } - - for (int i = 0; i < nSelectors; i++) - { - ll_i = selector[i]; - int j = 0; - tmp = pos[j]; - while (ll_i != tmp) - { - j++; - tmp2 = tmp; - tmp = pos[j]; - pos[j] = tmp2; - } - pos[0] = tmp; - selectorMtf[i] = (char)j; - } - - int[][] code = new int[BZip2Constants.GroupCount][]; - - for (int i = 0; i < BZip2Constants.GroupCount; ++i) - { - code[i] = new int[BZip2Constants.MaximumAlphaSize]; - } - - /*--- Assign actual codes for the tables. --*/ - for (int t = 0; t < nGroups; t++) - { - minLen = 32; - maxLen = 0; - for (int i = 0; i < alphaSize; i++) - { - if (len[t][i] > maxLen) - { - maxLen = len[t][i]; - } - if (len[t][i] < minLen) - { - minLen = len[t][i]; - } - } - if (maxLen > 20) - { - Panic(); - } - if (minLen < 1) - { - Panic(); - } - HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); - } - - /*--- Transmit the mapping table. ---*/ - bool[] inUse16 = new bool[16]; - for (int i = 0; i < 16; ++i) - { - inUse16[i] = false; - for (int j = 0; j < 16; ++j) - { - if (inUse[i * 16 + j]) - { - inUse16[i] = true; - } - } - } - - for (int i = 0; i < 16; ++i) - { - if (inUse16[i]) - { - BsW(1, 1); - } - else - { - BsW(1, 0); - } - } - - for (int i = 0; i < 16; ++i) - { - if (inUse16[i]) - { - for (int j = 0; j < 16; ++j) - { - if (inUse[i * 16 + j]) - { - BsW(1, 1); - } - else - { - BsW(1, 0); - } - } - } - } - - /*--- Now the selectors. ---*/ - BsW(3, nGroups); - BsW(15, nSelectors); - for (int i = 0; i < nSelectors; ++i) - { - for (int j = 0; j < selectorMtf[i]; ++j) - { - BsW(1, 1); - } - BsW(1, 0); - } - - /*--- Now the coding tables. ---*/ - for (int t = 0; t < nGroups; ++t) - { - int curr = len[t][0]; - BsW(5, curr); - for (int i = 0; i < alphaSize; ++i) - { - while (curr < len[t][i]) - { - BsW(2, 2); - curr++; /* 10 */ - } - while (curr > len[t][i]) - { - BsW(2, 3); - curr--; /* 11 */ - } - BsW(1, 0); - } - } - - /*--- And finally, the block data proper ---*/ - selCtr = 0; - gs = 0; - while (true) - { - if (gs >= nMTF) - { - break; - } - ge = gs + BZip2Constants.GroupSize - 1; - if (ge >= nMTF) - { - ge = nMTF - 1; - } - - for (int i = gs; i <= ge; i++) - { - BsW(len[selector[selCtr]][szptr[i]], code[selector[selCtr]][szptr[i]]); - } - - gs = ge + 1; - ++selCtr; - } - if (!(selCtr == nSelectors)) - { - Panic(); - } - } - - private void MoveToFrontCodeAndSend() - { - BsPutIntVS(24, origPtr); - GenerateMTFValues(); - SendMTFValues(); - } - - private void SimpleSort(int lo, int hi, int d) - { - int i, j, h, bigN, hp; - int v; - - bigN = hi - lo + 1; - if (bigN < 2) - { - return; - } - - hp = 0; - while (increments[hp] < bigN) - { - hp++; - } - hp--; - - for (; hp >= 0; hp--) - { - h = increments[hp]; - - i = lo + h; - while (true) - { - /*-- copy 1 --*/ - if (i > hi) - break; - v = zptr[i]; - j = i; - while (FullGtU(zptr[j - h] + d, v + d)) - { - zptr[j] = zptr[j - h]; - j = j - h; - if (j <= (lo + h - 1)) - break; - } - zptr[j] = v; - i++; - - /*-- copy 2 --*/ - if (i > hi) - { - break; - } - v = zptr[i]; - j = i; - while (FullGtU(zptr[j - h] + d, v + d)) - { - zptr[j] = zptr[j - h]; - j = j - h; - if (j <= (lo + h - 1)) - { - break; - } - } - zptr[j] = v; - i++; - - /*-- copy 3 --*/ - if (i > hi) - { - break; - } - v = zptr[i]; - j = i; - while (FullGtU(zptr[j - h] + d, v + d)) - { - zptr[j] = zptr[j - h]; - j = j - h; - if (j <= (lo + h - 1)) - { - break; - } - } - zptr[j] = v; - i++; - - if (workDone > workLimit && firstAttempt) - { - return; - } - } - } - } - - private void Vswap(int p1, int p2, int n) - { - int temp = 0; - while (n > 0) - { - temp = zptr[p1]; - zptr[p1] = zptr[p2]; - zptr[p2] = temp; - p1++; - p2++; - n--; - } - } - - private void QSort3(int loSt, int hiSt, int dSt) - { - int unLo, unHi, ltLo, gtHi, med, n, m; - int lo, hi, d; - - StackElement[] stack = new StackElement[QSORT_STACK_SIZE]; - - int sp = 0; - - stack[sp].ll = loSt; - stack[sp].hh = hiSt; - stack[sp].dd = dSt; - sp++; - - while (sp > 0) - { - if (sp >= QSORT_STACK_SIZE) - { - Panic(); - } - - sp--; - lo = stack[sp].ll; - hi = stack[sp].hh; - d = stack[sp].dd; - - if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) - { - SimpleSort(lo, hi, d); - if (workDone > workLimit && firstAttempt) - { - return; - } - continue; - } - - med = Med3(block[zptr[lo] + d + 1], - block[zptr[hi] + d + 1], - block[zptr[(lo + hi) >> 1] + d + 1]); - - unLo = ltLo = lo; - unHi = gtHi = hi; - - while (true) - { - while (true) - { - if (unLo > unHi) - { - break; - } - n = ((int)block[zptr[unLo] + d + 1]) - med; - if (n == 0) - { - int temp = zptr[unLo]; - zptr[unLo] = zptr[ltLo]; - zptr[ltLo] = temp; - ltLo++; - unLo++; - continue; - } - if (n > 0) - { - break; - } - unLo++; - } - - while (true) - { - if (unLo > unHi) - { - break; - } - n = ((int)block[zptr[unHi] + d + 1]) - med; - if (n == 0) - { - int temp = zptr[unHi]; - zptr[unHi] = zptr[gtHi]; - zptr[gtHi] = temp; - gtHi--; - unHi--; - continue; - } - if (n < 0) - { - break; - } - unHi--; - } - - if (unLo > unHi) - { - break; - } - - { - int temp = zptr[unLo]; - zptr[unLo] = zptr[unHi]; - zptr[unHi] = temp; - unLo++; - unHi--; - } - } - - if (gtHi < ltLo) - { - stack[sp].ll = lo; - stack[sp].hh = hi; - stack[sp].dd = d + 1; - sp++; - continue; - } - - n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); - Vswap(lo, unLo - n, n); - m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); - Vswap(unLo, hi - m + 1, m); - - n = lo + unLo - ltLo - 1; - m = hi - (gtHi - unHi) + 1; - - stack[sp].ll = lo; - stack[sp].hh = n; - stack[sp].dd = d; - sp++; - - stack[sp].ll = n + 1; - stack[sp].hh = m - 1; - stack[sp].dd = d + 1; - sp++; - - stack[sp].ll = m; - stack[sp].hh = hi; - stack[sp].dd = d; - sp++; - } - } - - private void MainSort() - { - int i, j, ss, sb; - int[] runningOrder = new int[256]; - int[] copy = new int[256]; - bool[] bigDone = new bool[256]; - int c1, c2; - int numQSorted; - - /*-- - In the various block-sized structures, live data runs - from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, - set up the overshoot area for block. - --*/ - - // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); - for (i = 0; i < BZip2Constants.OvershootBytes; i++) - { - block[last + i + 2] = block[(i % (last + 1)) + 1]; - } - for (i = 0; i <= last + BZip2Constants.OvershootBytes; i++) - { - quadrant[i] = 0; - } - - block[0] = (byte)(block[last + 1]); - - if (last < 4000) - { - /*-- - Use simpleSort(), since the full sorting mechanism - has quite a large constant overhead. - --*/ - for (i = 0; i <= last; i++) - { - zptr[i] = i; - } - firstAttempt = false; - workDone = workLimit = 0; - SimpleSort(0, last, 0); - } - else - { - numQSorted = 0; - for (i = 0; i <= 255; i++) - { - bigDone[i] = false; - } - for (i = 0; i <= 65536; i++) - { - ftab[i] = 0; - } - - c1 = block[0]; - for (i = 0; i <= last; i++) - { - c2 = block[i + 1]; - ftab[(c1 << 8) + c2]++; - c1 = c2; - } - - for (i = 1; i <= 65536; i++) - { - ftab[i] += ftab[i - 1]; - } - - c1 = block[1]; - for (i = 0; i < last; i++) - { - c2 = block[i + 2]; - j = (c1 << 8) + c2; - c1 = c2; - ftab[j]--; - zptr[ftab[j]] = i; - } - - j = ((block[last + 1]) << 8) + (block[1]); - ftab[j]--; - zptr[ftab[j]] = last; - - /*-- - Now ftab contains the first loc of every small bucket. - Calculate the running order, from smallest to largest - big bucket. - --*/ - - for (i = 0; i <= 255; i++) - { - runningOrder[i] = i; - } - - int vv; - int h = 1; - do - { - h = 3 * h + 1; - } while (h <= 256); - do - { - h = h / 3; - for (i = h; i <= 255; i++) - { - vv = runningOrder[i]; - j = i; - while ((ftab[((runningOrder[j - h]) + 1) << 8] - ftab[(runningOrder[j - h]) << 8]) > (ftab[((vv) + 1) << 8] - ftab[(vv) << 8])) - { - runningOrder[j] = runningOrder[j - h]; - j = j - h; - if (j <= (h - 1)) - { - break; - } - } - runningOrder[j] = vv; - } - } while (h != 1); - - /*-- - The main sorting loop. - --*/ - for (i = 0; i <= 255; i++) - { - /*-- - Process big buckets, starting with the least full. - --*/ - ss = runningOrder[i]; - - /*-- - Complete the big bucket [ss] by quicksorting - any unsorted small buckets [ss, j]. Hopefully - previous pointer-scanning phases have already - completed many of the small buckets [ss, j], so - we don't have to sort them at all. - --*/ - for (j = 0; j <= 255; j++) - { - sb = (ss << 8) + j; - if (!((ftab[sb] & SETMASK) == SETMASK)) - { - int lo = ftab[sb] & CLEARMASK; - int hi = (ftab[sb + 1] & CLEARMASK) - 1; - if (hi > lo) - { - QSort3(lo, hi, 2); - numQSorted += (hi - lo + 1); - if (workDone > workLimit && firstAttempt) - { - return; - } - } - ftab[sb] |= SETMASK; - } - } - - /*-- - The ss big bucket is now done. Record this fact, - and update the quadrant descriptors. Remember to - update quadrants in the overshoot area too, if - necessary. The "if (i < 255)" test merely skips - this updating for the last bucket processed, since - updating for the last bucket is pointless. - --*/ - bigDone[ss] = true; - - if (i < 255) - { - int bbStart = ftab[ss << 8] & CLEARMASK; - int bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; - int shifts = 0; - - while ((bbSize >> shifts) > 65534) - { - shifts++; - } - - for (j = 0; j < bbSize; j++) - { - int a2update = zptr[bbStart + j]; - int qVal = (j >> shifts); - quadrant[a2update] = qVal; - if (a2update < BZip2Constants.OvershootBytes) - { - quadrant[a2update + last + 1] = qVal; - } - } - - if (!(((bbSize - 1) >> shifts) <= 65535)) - { - Panic(); - } - } - - /*-- - Now scan this big bucket so as to synthesise the - sorted order for small buckets [t, ss] for all t != ss. - --*/ - for (j = 0; j <= 255; j++) - { - copy[j] = ftab[(j << 8) + ss] & CLEARMASK; - } - - for (j = ftab[ss << 8] & CLEARMASK; j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) - { - c1 = block[zptr[j]]; - if (!bigDone[c1]) - { - zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; - copy[c1]++; - } - } - - for (j = 0; j <= 255; j++) - { - ftab[(j << 8) + ss] |= SETMASK; - } - } - } - } - - private void RandomiseBlock() - { - int i; - int rNToGo = 0; - int rTPos = 0; - for (i = 0; i < 256; i++) - { - inUse[i] = false; - } - - for (i = 0; i <= last; i++) - { - if (rNToGo == 0) - { - rNToGo = (int)BZip2Constants.RandomNumbers[rTPos]; - rTPos++; - if (rTPos == 512) - { - rTPos = 0; - } - } - rNToGo--; - block[i + 1] ^= (byte)((rNToGo == 1) ? 1 : 0); - // handle 16 bit signed numbers - block[i + 1] &= 0xFF; - - inUse[block[i + 1]] = true; - } - } - - private void DoReversibleTransformation() - { - workLimit = workFactor * last; - workDone = 0; - blockRandomised = false; - firstAttempt = true; - - MainSort(); - - if (workDone > workLimit && firstAttempt) - { - RandomiseBlock(); - workLimit = workDone = 0; - blockRandomised = true; - firstAttempt = false; - MainSort(); - } - - origPtr = -1; - for (int i = 0; i <= last; i++) - { - if (zptr[i] == 0) - { - origPtr = i; - break; - } - } - - if (origPtr == -1) - { - Panic(); - } - } - - private bool FullGtU(int i1, int i2) - { - int k; - byte c1, c2; - int s1, s2; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - i1++; - i2++; - - k = last + 1; - - do - { - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return s1 > s2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return s1 > s2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return s1 > s2; - } - i1++; - i2++; - - c1 = block[i1 + 1]; - c2 = block[i2 + 1]; - if (c1 != c2) - { - return c1 > c2; - } - s1 = quadrant[i1]; - s2 = quadrant[i2]; - if (s1 != s2) - { - return s1 > s2; - } - i1++; - i2++; - - if (i1 > last) - { - i1 -= last; - i1--; - } - if (i2 > last) - { - i2 -= last; - i2--; - } - - k -= 4; - ++workDone; - } while (k >= 0); - - return false; - } - - private void AllocateCompressStructures() - { - int n = BZip2Constants.BaseBlockSize * blockSize100k; - block = new byte[(n + 1 + BZip2Constants.OvershootBytes)]; - quadrant = new int[(n + BZip2Constants.OvershootBytes)]; - zptr = new int[n]; - ftab = new int[65537]; - - if (block == null || quadrant == null || zptr == null || ftab == null) - { - // int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; - // compressOutOfMemory ( totalDraw, n ); - } - - /* - The back end needs a place to store the MTF values - whilst it calculates the coding tables. We could - put them in the zptr array. However, these values - will fit in a short, so we overlay szptr at the - start of zptr, in the hope of reducing the number - of cache misses induced by the multiple traversals - of the MTF values when calculating coding tables. - Seems to improve compression speed by about 1%. - */ - // szptr = zptr; - - szptr = new short[2 * n]; - } - - private void GenerateMTFValues() - { - char[] yy = new char[256]; - int i, j; - char tmp; - char tmp2; - int zPend; - int wr; - int EOB; - - MakeMaps(); - EOB = nInUse + 1; - - for (i = 0; i <= EOB; i++) - { - mtfFreq[i] = 0; - } - - wr = 0; - zPend = 0; - for (i = 0; i < nInUse; i++) - { - yy[i] = (char)i; - } - - for (i = 0; i <= last; i++) - { - char ll_i; - - ll_i = unseqToSeq[block[zptr[i]]]; - - j = 0; - tmp = yy[j]; - while (ll_i != tmp) - { - j++; - tmp2 = tmp; - tmp = yy[j]; - yy[j] = tmp2; - } - yy[0] = tmp; - - if (j == 0) - { - zPend++; - } - else - { - if (zPend > 0) - { - zPend--; - while (true) - { - switch (zPend % 2) - { - case 0: - szptr[wr] = (short)BZip2Constants.RunA; - wr++; - mtfFreq[BZip2Constants.RunA]++; - break; - - case 1: - szptr[wr] = (short)BZip2Constants.RunB; - wr++; - mtfFreq[BZip2Constants.RunB]++; - break; - } - if (zPend < 2) - { - break; - } - zPend = (zPend - 2) / 2; - } - zPend = 0; - } - szptr[wr] = (short)(j + 1); - wr++; - mtfFreq[j + 1]++; - } - } - - if (zPend > 0) - { - zPend--; - while (true) - { - switch (zPend % 2) - { - case 0: - szptr[wr] = (short)BZip2Constants.RunA; - wr++; - mtfFreq[BZip2Constants.RunA]++; - break; - - case 1: - szptr[wr] = (short)BZip2Constants.RunB; - wr++; - mtfFreq[BZip2Constants.RunB]++; - break; - } - if (zPend < 2) - { - break; - } - zPend = (zPend - 2) / 2; - } - } - - szptr[wr] = (short)EOB; - wr++; - mtfFreq[EOB]++; - - nMTF = wr; - } - - private static void Panic() - { - throw new BZip2Exception("BZip2 output stream panic"); - } - - private static void HbMakeCodeLengths(char[] len, int[] freq, int alphaSize, int maxLen) - { - /*-- - Nodes and heap entries run from 1. Entry 0 - for both the heap and nodes is a sentinel. - --*/ - int nNodes, nHeap, n1, n2, j, k; - bool tooLong; - - int[] heap = new int[BZip2Constants.MaximumAlphaSize + 2]; - int[] weight = new int[BZip2Constants.MaximumAlphaSize * 2]; - int[] parent = new int[BZip2Constants.MaximumAlphaSize * 2]; - - for (int i = 0; i < alphaSize; ++i) - { - weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; - } - - while (true) - { - nNodes = alphaSize; - nHeap = 0; - - heap[0] = 0; - weight[0] = 0; - parent[0] = -2; - - for (int i = 1; i <= alphaSize; ++i) - { - parent[i] = -1; - nHeap++; - heap[nHeap] = i; - int zz = nHeap; - int tmp = heap[zz]; - while (weight[tmp] < weight[heap[zz >> 1]]) - { - heap[zz] = heap[zz >> 1]; - zz >>= 1; - } - heap[zz] = tmp; - } - if (!(nHeap < (BZip2Constants.MaximumAlphaSize + 2))) - { - Panic(); - } - - while (nHeap > 1) - { - n1 = heap[1]; - heap[1] = heap[nHeap]; - nHeap--; - int zz = 1; - int yy = 0; - int tmp = heap[zz]; - while (true) - { - yy = zz << 1; - if (yy > nHeap) - { - break; - } - if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) - { - yy++; - } - if (weight[tmp] < weight[heap[yy]]) - { - break; - } - - heap[zz] = heap[yy]; - zz = yy; - } - heap[zz] = tmp; - n2 = heap[1]; - heap[1] = heap[nHeap]; - nHeap--; - - zz = 1; - yy = 0; - tmp = heap[zz]; - while (true) - { - yy = zz << 1; - if (yy > nHeap) - { - break; - } - if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) - { - yy++; - } - if (weight[tmp] < weight[heap[yy]]) - { - break; - } - heap[zz] = heap[yy]; - zz = yy; - } - heap[zz] = tmp; - nNodes++; - parent[n1] = parent[n2] = nNodes; - - weight[nNodes] = (int)((weight[n1] & 0xffffff00) + (weight[n2] & 0xffffff00)) | - (int)(1 + (((weight[n1] & 0x000000ff) > (weight[n2] & 0x000000ff)) ? (weight[n1] & 0x000000ff) : (weight[n2] & 0x000000ff))); - - parent[nNodes] = -1; - nHeap++; - heap[nHeap] = nNodes; - - zz = nHeap; - tmp = heap[zz]; - while (weight[tmp] < weight[heap[zz >> 1]]) - { - heap[zz] = heap[zz >> 1]; - zz >>= 1; - } - heap[zz] = tmp; - } - if (!(nNodes < (BZip2Constants.MaximumAlphaSize * 2))) - { - Panic(); - } - - tooLong = false; - for (int i = 1; i <= alphaSize; ++i) - { - j = 0; - k = i; - while (parent[k] >= 0) - { - k = parent[k]; - j++; - } - len[i - 1] = (char)j; - tooLong |= j > maxLen; - } - - if (!tooLong) - { - break; - } - - for (int i = 1; i < alphaSize; ++i) - { - j = weight[i] >> 8; - j = 1 + (j / 2); - weight[i] = j << 8; - } - } - } - - private static void HbAssignCodes(int[] code, char[] length, int minLen, int maxLen, int alphaSize) - { - int vec = 0; - for (int n = minLen; n <= maxLen; ++n) - { - for (int i = 0; i < alphaSize; ++i) - { - if (length[i] == n) - { - code[i] = vec; - ++vec; - } - } - vec <<= 1; - } - } - - private static byte Med3(byte a, byte b, byte c) - { - byte t; - if (a > b) - { - t = a; - a = b; - b = t; - } - if (b > c) - { - t = b; - b = c; - c = t; - } - if (a > b) - { - b = a; - } - return b; - } - - private struct StackElement - { - public int ll; - public int hh; - public int dd; - } - } + #region Constants + + private const int SETMASK = 1 << 21; + private const int CLEARMASK = ~SETMASK; + private const int GREATER_ICOST = 15; + private const int LESSER_ICOST = 0; + private const int SMALL_THRESH = 20; + private const int DEPTH_THRESH = 10; + + /*-- + If you are ever unlucky/improbable enough + to get a stack overflow whilst sorting, + increase the following constant and try + again. In practice I have never seen the + stack go above 27 elems, so the following + limit seems very generous. + --*/ + private const int QSORT_STACK_SIZE = 1000; + + /*-- + Knuth's increments seem to work better + than Incerpi-Sedgewick here. Possibly + because the number of elems to sort is + usually small, typically <= 20. + --*/ + + private readonly int[] increments = { + 1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484 + }; + + #endregion Constants + + #region Instance Fields + + /*-- + index of the last char in the block, so + the block size == last + 1. + --*/ + private int last; + + /*-- + index in zptr[] of original string after sorting. + --*/ + private int origPtr; + + /*-- + always: in the range 0 .. 9. + The current block size is 100000 * this number. + --*/ + private readonly int blockSize100k; + + private bool blockRandomised; + + private int bytesOut; + private int bsBuff; + private int bsLive; + private readonly IChecksum mCrc = new BZip2Crc(); + + private readonly bool[] inUse = new bool[256]; + private int nInUse; + + private readonly char[] seqToUnseq = new char[256]; + private readonly char[] unseqToSeq = new char[256]; + + private readonly char[] selector = new char[BZip2Constants.MaximumSelectors]; + private readonly char[] selectorMtf = new char[BZip2Constants.MaximumSelectors]; + + private byte[] block; + private int[] quadrant; + private int[] zptr; + private short[] szptr; + private int[] ftab; + + private int nMTF; + + private readonly int[] mtfFreq = new int[BZip2Constants.MaximumAlphaSize]; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private readonly int workFactor; + private int workDone; + private int workLimit; + private bool firstAttempt; + private int nBlocksRandomised; + + private int currentChar = -1; + private int runLength; + private uint blockCRC, combinedCRC; + private int allowableBlockSize; + private readonly Stream baseStream; + private bool disposed_; + + #endregion Instance Fields + + /// + /// Construct a default output stream with maximum block size + /// + /// The stream to write BZip data onto. + public BZip2OutputStream(Stream stream) : this(stream, 9) + { + } + + /// + /// Initialise a new instance of the + /// for the specified stream, using the given blocksize. + /// + /// The stream to write compressed data to. + /// The block size to use. + /// + /// Valid block sizes are in the range 1..9, with 1 giving + /// the lowest compression and 9 the highest. + /// + public BZip2OutputStream(Stream stream, int blockSize) + { + baseStream = stream ?? throw new ArgumentNullException(nameof(stream)); + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + + workFactor = 50; + if (blockSize > 9) + { + blockSize = 9; + } + + if (blockSize < 1) + { + blockSize = 1; + } + blockSize100k = blockSize; + AllocateCompressStructures(); + Initialize(); + InitBlock(); + } + + /// + /// Ensures that resources are freed and other cleanup operations + /// are performed when the garbage collector reclaims the BZip2OutputStream. + /// + ~BZip2OutputStream() + { + Dispose(false); + } + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return false; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing + /// + public override bool CanWrite + { + get + { + return baseStream.CanWrite; + } + } + + /// + /// Gets the length in bytes of the stream + /// + public override long Length + { + get + { + return baseStream.Length; + } + } + + /// + /// Gets or sets the current position of this stream. + /// + public override long Position + { + get + { + return baseStream.Position; + } + set + { + throw new NotSupportedException("BZip2OutputStream position cannot be set"); + } + } + + /// + /// Sets the current position of this stream to the given value. + /// + /// The point relative to the offset from which to being seeking. + /// The reference point from which to begin seeking. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("BZip2OutputStream Seek not supported"); + } + + /// + /// Sets the length of this stream to the given value. + /// + /// The new stream length. + public override void SetLength(long value) + { + throw new NotSupportedException("BZip2OutputStream SetLength not supported"); + } + + /// + /// Read a byte from the stream advancing the position. + /// + /// The byte read cast to an int; -1 if end of stream. + public override int ReadByte() + { + throw new NotSupportedException("BZip2OutputStream ReadByte not supported"); + } + + /// + /// Read a block of bytes + /// + /// The buffer to read into. + /// The offset in the buffer to start storing data at. + /// The maximum number of bytes to read. + /// The total number of bytes read. This might be less than the number of bytes + /// requested if that number of bytes are not currently available, or zero + /// if the end of the stream is reached. + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("BZip2OutputStream Read not supported"); + } + + /// + /// Write a block of bytes to the stream + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (buffer.Length - offset < count) + { + throw new ArgumentException("Offset/count out of range"); + } + + for (var i = 0; i < count; ++i) + { + WriteByte(buffer[offset + i]); + } + } + + /// + /// Write a byte to the stream. + /// + /// The byte to write to the stream. + public override void WriteByte(byte value) + { + var b = (256 + value) % 256; + if (currentChar != -1) + { + if (currentChar == b) + { + runLength++; + if (runLength > 254) + { + WriteRun(); + currentChar = -1; + runLength = 0; + } + } + else + { + WriteRun(); + runLength = 1; + currentChar = b; + } + } + else + { + currentChar = b; + runLength++; + } + } + + private void MakeMaps() + { + nInUse = 0; + for (var i = 0; i < 256; i++) + { + if (inUse[i]) + { + seqToUnseq[nInUse] = (char)i; + unseqToSeq[i] = (char)nInUse; + nInUse++; + } + } + } + + /// + /// Get the number of bytes written to output. + /// + private void WriteRun() + { + if (last < allowableBlockSize) + { + inUse[currentChar] = true; + for (var i = 0; i < runLength; i++) + { + mCrc.Update(currentChar); + } + + switch (runLength) + { + case 1: + last++; + block[last + 1] = (byte)currentChar; + break; + + case 2: + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + break; + + case 3: + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + break; + + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)(runLength - 4); + break; + } + } + else + { + EndBlock(); + InitBlock(); + WriteRun(); + } + } + + /// + /// Get the number of bytes written to the output. + /// + public int BytesWritten + { + get { return bytesOut; } + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + try + { + try + { + base.Dispose(disposing); + if (!disposed_) + { + disposed_ = true; + + if (runLength > 0) + { + WriteRun(); + } + + currentChar = -1; + EndBlock(); + EndCompression(); + Flush(); + } + } + finally + { + if (disposing) + { + if (IsStreamOwner) + { + baseStream.Dispose(); + } + } + } + } + catch + { + } + } + + /// + /// Flush output buffers + /// + public override void Flush() + { + baseStream.Flush(); + } + + private void Initialize() + { + bytesOut = 0; + nBlocksRandomised = 0; + + /*--- Write header `magic' bytes indicating file-format == huffmanised, + followed by a digit indicating blockSize100k. + ---*/ + + BsPutUChar('B'); + BsPutUChar('Z'); + + BsPutUChar('h'); + BsPutUChar('0' + blockSize100k); + + combinedCRC = 0; + } + + private void InitBlock() + { + mCrc.Reset(); + last = -1; + + for (var i = 0; i < 256; i++) + { + inUse[i] = false; + } + + /*--- 20 is just a paranoia constant ---*/ + allowableBlockSize = (BZip2Constants.BaseBlockSize * blockSize100k) - 20; + } + + private void EndBlock() + { + if (last < 0) + { // dont do anything for empty files, (makes empty files compatible with original Bzip) + return; + } + + blockCRC = unchecked((uint)mCrc.Value); + combinedCRC = (combinedCRC << 1) | (combinedCRC >> 31); + combinedCRC ^= blockCRC; + + /*-- sort the block and establish position of original string --*/ + DoReversibleTransformation(); + + /*-- + A 6-byte block header, the value chosen arbitrarily + as 0x314159265359 :-). A 32 bit value does not really + give a strong enough guarantee that the value will not + appear by chance in the compressed datastream. Worst-case + probability of this event, for a 900k block, is about + 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + For a compressed file of size 100Gb -- about 100000 blocks -- + only a 48-bit marker will do. NB: normal compression/ + decompression do *not* rely on these statistical properties. + They are only important when trying to recover blocks from + damaged files. + --*/ + BsPutUChar(0x31); + BsPutUChar(0x41); + BsPutUChar(0x59); + BsPutUChar(0x26); + BsPutUChar(0x53); + BsPutUChar(0x59); + + /*-- Now the block's CRC, so it is in a known place. --*/ + unchecked + { + BsPutint((int)blockCRC); + } + + /*-- Now a single bit indicating randomisation. --*/ + if (blockRandomised) + { + BsW(1, 1); + nBlocksRandomised++; + } + else + { + BsW(1, 0); + } + + /*-- Finally, block's contents proper. --*/ + MoveToFrontCodeAndSend(); + } + + private void EndCompression() + { + /*-- + Now another magic 48-bit number, 0x177245385090, to + indicate the end of the last block. (sqrt(pi), if + you want to know. I did want to use e, but it contains + too much repetition -- 27 18 28 18 28 46 -- for me + to feel statistically comfortable. Call me paranoid.) + --*/ + BsPutUChar(0x17); + BsPutUChar(0x72); + BsPutUChar(0x45); + BsPutUChar(0x38); + BsPutUChar(0x50); + BsPutUChar(0x90); + + unchecked + { + BsPutint((int)combinedCRC); + } + + BsFinishedWithStream(); + } + + private void BsFinishedWithStream() + { + while (bsLive > 0) + { + var ch = bsBuff >> 24; + baseStream.WriteByte((byte)ch); // write 8-bit + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void BsW(int n, int v) + { + while (bsLive >= 8) + { + var ch = bsBuff >> 24; + unchecked + { + baseStream.WriteByte((byte)ch); + } // write 8-bit + bsBuff <<= 8; + bsLive -= 8; + ++bytesOut; + } + bsBuff |= v << (32 - bsLive - n); + bsLive += n; + } + + private void BsPutUChar(int c) + { + BsW(8, c); + } + + private void BsPutint(int u) + { + BsW(8, (u >> 24) & 0xFF); + BsW(8, (u >> 16) & 0xFF); + BsW(8, (u >> 8) & 0xFF); + BsW(8, u & 0xFF); + } + + private void BsPutIntVS(int numBits, int c) + { + BsW(numBits, c); + } + + private void SendMTFValues() + { + var len = new char[BZip2Constants.GroupCount][]; + for (var i = 0; i < BZip2Constants.GroupCount; ++i) + { + len[i] = new char[BZip2Constants.MaximumAlphaSize]; + } + + int gs, ge, totc, bt, bc, iter; + int nSelectors = 0, alphaSize, minLen, maxLen, selCtr; + int nGroups; + + alphaSize = nInUse + 2; + for (var t = 0; t < BZip2Constants.GroupCount; t++) + { + for (var v = 0; v < alphaSize; v++) + { + len[t][v] = (char)GREATER_ICOST; + } + } + + /*--- Decide how many coding tables to use ---*/ + if (nMTF <= 0) + { + Panic(); + } + + if (nMTF < 200) + { + nGroups = 2; + } + else if (nMTF < 600) + { + nGroups = 3; + } + else if (nMTF < 1200) + { + nGroups = 4; + } + else + { + nGroups = nMTF < 2400 ? 5 : 6; + } + + /*--- Generate an initial set of coding tables ---*/ + var nPart = nGroups; + var remF = nMTF; + gs = 0; + while (nPart > 0) + { + var tFreq = remF / nPart; + var aFreq = 0; + ge = gs - 1; + while (aFreq < tFreq && ge < alphaSize - 1) + { + ge++; + aFreq += mtfFreq[ge]; + } + + if (ge > gs && nPart != nGroups && nPart != 1 && ((nGroups - nPart) % 2 == 1)) + { + aFreq -= mtfFreq[ge]; + ge--; + } + + for (var v = 0; v < alphaSize; v++) + { + len[nPart - 1][v] = v >= gs && v <= ge ? (char)LESSER_ICOST : (char)GREATER_ICOST; + } + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + + var rfreq = new int[BZip2Constants.GroupCount][]; + for (var i = 0; i < BZip2Constants.GroupCount; ++i) + { + rfreq[i] = new int[BZip2Constants.MaximumAlphaSize]; + } + + var fave = new int[BZip2Constants.GroupCount]; + var cost = new short[BZip2Constants.GroupCount]; + /*--- + Iterate up to N_ITERS times to improve the tables. + ---*/ + for (iter = 0; iter < BZip2Constants.NumberOfIterations; ++iter) + { + for (var t = 0; t < nGroups; ++t) + { + fave[t] = 0; + } + + for (var t = 0; t < nGroups; ++t) + { + for (var v = 0; v < alphaSize; ++v) + { + rfreq[t][v] = 0; + } + } + + nSelectors = 0; + totc = 0; + gs = 0; + while (true) + { + /*--- Set group start & end marks. --*/ + if (gs >= nMTF) + { + break; + } + ge = gs + BZip2Constants.GroupSize - 1; + if (ge >= nMTF) + { + ge = nMTF - 1; + } + + /*-- + Calculate the cost of this group as coded + by each of the coding tables. + --*/ + for (var t = 0; t < nGroups; t++) + { + cost[t] = 0; + } + + if (nGroups == 6) + { + short cost0, cost1, cost2, cost3, cost4, cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for (var i = gs; i <= ge; ++i) + { + var icv = szptr[i]; + cost0 += (short)len[0][icv]; + cost1 += (short)len[1][icv]; + cost2 += (short)len[2][icv]; + cost3 += (short)len[3][icv]; + cost4 += (short)len[4][icv]; + cost5 += (short)len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } + else + { + for (var i = gs; i <= ge; ++i) + { + var icv = szptr[i]; + for (var t = 0; t < nGroups; t++) + { + cost[t] += (short)len[t][icv]; + } + } + } + + /*-- + Find the coding table which is best for this group, + and record its identity in the selector table. + --*/ + bc = 999999999; + bt = -1; + for (var t = 0; t < nGroups; ++t) + { + if (cost[t] < bc) + { + bc = cost[t]; + bt = t; + } + } + totc += bc; + fave[bt]++; + selector[nSelectors] = (char)bt; + nSelectors++; + + /*-- + Increment the symbol frequencies for the selected table. + --*/ + for (var i = gs; i <= ge; ++i) + { + ++rfreq[bt][szptr[i]]; + } + + gs = ge + 1; + } + + /*-- + Recompute the tables based on the accumulated frequencies. + --*/ + for (var t = 0; t < nGroups; ++t) + { + HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); + } + } + + if (!(nGroups < 8)) + { + Panic(); + } + + if (nSelectors is not (< 32768 and <= (2 + (900000 / BZip2Constants.GroupSize)))) + { + Panic(); + } + + /*--- Compute MTF values for the selectors. ---*/ + var pos = new char[BZip2Constants.GroupCount]; + char ll_i, tmp2, tmp; + + for (var i = 0; i < nGroups; i++) + { + pos[i] = (char)i; + } + + for (var i = 0; i < nSelectors; i++) + { + ll_i = selector[i]; + var j = 0; + tmp = pos[j]; + while (ll_i != tmp) + { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = (char)j; + } + + var code = new int[BZip2Constants.GroupCount][]; + + for (var i = 0; i < BZip2Constants.GroupCount; ++i) + { + code[i] = new int[BZip2Constants.MaximumAlphaSize]; + } + + /*--- Assign actual codes for the tables. --*/ + for (var t = 0; t < nGroups; t++) + { + minLen = 32; + maxLen = 0; + for (var i = 0; i < alphaSize; i++) + { + if (len[t][i] > maxLen) + { + maxLen = len[t][i]; + } + if (len[t][i] < minLen) + { + minLen = len[t][i]; + } + } + if (maxLen > 20) + { + Panic(); + } + if (minLen < 1) + { + Panic(); + } + HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); + } + + /*--- Transmit the mapping table. ---*/ + var inUse16 = new bool[16]; + for (var i = 0; i < 16; ++i) + { + inUse16[i] = false; + for (var j = 0; j < 16; ++j) + { + if (inUse[(i * 16) + j]) + { + inUse16[i] = true; + } + } + } + + for (var i = 0; i < 16; ++i) + { + if (inUse16[i]) + { + BsW(1, 1); + } + else + { + BsW(1, 0); + } + } + + for (var i = 0; i < 16; ++i) + { + if (inUse16[i]) + { + for (var j = 0; j < 16; ++j) + { + if (inUse[(i * 16) + j]) + { + BsW(1, 1); + } + else + { + BsW(1, 0); + } + } + } + } + + /*--- Now the selectors. ---*/ + BsW(3, nGroups); + BsW(15, nSelectors); + for (var i = 0; i < nSelectors; ++i) + { + for (var j = 0; j < selectorMtf[i]; ++j) + { + BsW(1, 1); + } + BsW(1, 0); + } + + /*--- Now the coding tables. ---*/ + for (var t = 0; t < nGroups; ++t) + { + int curr = len[t][0]; + BsW(5, curr); + for (var i = 0; i < alphaSize; ++i) + { + while (curr < len[t][i]) + { + BsW(2, 2); + curr++; /* 10 */ + } + while (curr > len[t][i]) + { + BsW(2, 3); + curr--; /* 11 */ + } + BsW(1, 0); + } + } + + /*--- And finally, the block data proper ---*/ + selCtr = 0; + gs = 0; + while (true) + { + if (gs >= nMTF) + { + break; + } + ge = gs + BZip2Constants.GroupSize - 1; + if (ge >= nMTF) + { + ge = nMTF - 1; + } + + for (var i = gs; i <= ge; i++) + { + BsW(len[selector[selCtr]][szptr[i]], code[selector[selCtr]][szptr[i]]); + } + + gs = ge + 1; + ++selCtr; + } + if (!(selCtr == nSelectors)) + { + Panic(); + } + } + + private void MoveToFrontCodeAndSend() + { + BsPutIntVS(24, origPtr); + GenerateMTFValues(); + SendMTFValues(); + } + + private void SimpleSort(int lo, int hi, int d) + { + int i, j, h, bigN, hp; + int v; + + bigN = hi - lo + 1; + if (bigN < 2) + { + return; + } + + hp = 0; + while (increments[hp] < bigN) + { + hp++; + } + hp--; + + for (; hp >= 0; hp--) + { + h = increments[hp]; + + i = lo + h; + while (true) + { + /*-- copy 1 --*/ + if (i > hi) + break; + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j -= h; + if (j <= (lo + h - 1)) + break; + } + zptr[j] = v; + i++; + + /*-- copy 2 --*/ + if (i > hi) + { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j -= h; + if (j <= (lo + h - 1)) + { + break; + } + } + zptr[j] = v; + i++; + + /*-- copy 3 --*/ + if (i > hi) + { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j -= h; + if (j <= (lo + h - 1)) + { + break; + } + } + zptr[j] = v; + i++; + + if (workDone > workLimit && firstAttempt) + { + return; + } + } + } + } + + private void Vswap(int p1, int p2, int n) + { + while (n > 0) + { + var temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private void QSort3(int loSt, int hiSt, int dSt) + { + int unLo, unHi, ltLo, gtHi, med, n, m; + int lo, hi, d; + + var stack = new StackElement[QSORT_STACK_SIZE]; + + var sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while (sp > 0) + { + if (sp >= QSORT_STACK_SIZE) + { + Panic(); + } + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) + { + SimpleSort(lo, hi, d); + if (workDone > workLimit && firstAttempt) + { + return; + } + continue; + } + + med = Med3(block[zptr[lo] + d + 1], + block[zptr[hi] + d + 1], + block[zptr[(lo + hi) >> 1] + d + 1]); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while (true) + { + while (true) + { + if (unLo > unHi) + { + break; + } + n = block[zptr[unLo] + d + 1] - med; + if (n == 0) + { + var temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + if (n > 0) + { + break; + } + unLo++; + } + + while (true) + { + if (unLo > unHi) + { + break; + } + n = block[zptr[unHi] + d + 1] - med; + if (n == 0) + { + var temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + if (n < 0) + { + break; + } + unHi--; + } + + if (unLo > unHi) + { + break; + } + + { + var temp = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = temp; + unLo++; + unHi--; + } + } + + if (gtHi < ltLo) + { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); + Vswap(lo, unLo - n, n); + m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); + Vswap(unLo, hi - m + 1, m); + + n = lo + unLo - ltLo - 1; + m = hi - (gtHi - unHi) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void MainSort() + { + int i, j, ss, sb; + var runningOrder = new int[256]; + var copy = new int[256]; + var bigDone = new bool[256]; + int c1, c2; + int numQSorted; + + /*-- + In the various block-sized structures, live data runs + from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + set up the overshoot area for block. + --*/ + + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for (i = 0; i < BZip2Constants.OvershootBytes; i++) + { + block[last + i + 2] = block[(i % (last + 1)) + 1]; + } + for (i = 0; i <= last + BZip2Constants.OvershootBytes; i++) + { + quadrant[i] = 0; + } + + block[0] = block[last + 1]; + + if (last < 4000) + { + /*-- + Use simpleSort(), since the full sorting mechanism + has quite a large constant overhead. + --*/ + for (i = 0; i <= last; i++) + { + zptr[i] = i; + } + firstAttempt = false; + workDone = workLimit = 0; + SimpleSort(0, last, 0); + } + else + { + numQSorted = 0; + for (i = 0; i <= 255; i++) + { + bigDone[i] = false; + } + for (i = 0; i <= 65536; i++) + { + ftab[i] = 0; + } + + c1 = block[0]; + for (i = 0; i <= last; i++) + { + c2 = block[i + 1]; + ftab[(c1 << 8) + c2]++; + c1 = c2; + } + + for (i = 1; i <= 65536; i++) + { + ftab[i] += ftab[i - 1]; + } + + c1 = block[1]; + for (i = 0; i < last; i++) + { + c2 = block[i + 2]; + j = (c1 << 8) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ((block[last + 1]) << 8) + block[1]; + ftab[j]--; + zptr[ftab[j]] = last; + + /*-- + Now ftab contains the first loc of every small bucket. + Calculate the running order, from smallest to largest + big bucket. + --*/ + + for (i = 0; i <= 255; i++) + { + runningOrder[i] = i; + } + + int vv; + var h = 1; + do + { + h = (3 * h) + 1; + } while (h <= 256); + do + { + h /= 3; + for (i = h; i <= 255; i++) + { + vv = runningOrder[i]; + j = i; + while ((ftab[(runningOrder[j - h] + 1) << 8] - ftab[(runningOrder[j - h]) << 8]) > (ftab[(vv + 1) << 8] - ftab[(vv) << 8])) + { + runningOrder[j] = runningOrder[j - h]; + j -= h; + if (j <= (h - 1)) + { + break; + } + } + runningOrder[j] = vv; + } + } while (h != 1); + + /*-- + The main sorting loop. + --*/ + for (i = 0; i <= 255; i++) + { + /*-- + Process big buckets, starting with the least full. + --*/ + ss = runningOrder[i]; + + /*-- + Complete the big bucket [ss] by quicksorting + any unsorted small buckets [ss, j]. Hopefully + previous pointer-scanning phases have already + completed many of the small buckets [ss, j], so + we don't have to sort them at all. + --*/ + for (j = 0; j <= 255; j++) + { + sb = (ss << 8) + j; + if (!((ftab[sb] & SETMASK) == SETMASK)) + { + var lo = ftab[sb] & CLEARMASK; + var hi = (ftab[sb + 1] & CLEARMASK) - 1; + if (hi > lo) + { + QSort3(lo, hi, 2); + numQSorted += hi - lo + 1; + if (workDone > workLimit && firstAttempt) + { + return; + } + } + ftab[sb] |= SETMASK; + } + } + + /*-- + The ss big bucket is now done. Record this fact, + and update the quadrant descriptors. Remember to + update quadrants in the overshoot area too, if + necessary. The "if (i < 255)" test merely skips + this updating for the last bucket processed, since + updating for the last bucket is pointless. + --*/ + bigDone[ss] = true; + + if (i < 255) + { + var bbStart = ftab[ss << 8] & CLEARMASK; + var bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; + var shifts = 0; + + while ((bbSize >> shifts) > 65534) + { + shifts++; + } + + for (j = 0; j < bbSize; j++) + { + var a2update = zptr[bbStart + j]; + var qVal = j >> shifts; + quadrant[a2update] = qVal; + if (a2update < BZip2Constants.OvershootBytes) + { + quadrant[a2update + last + 1] = qVal; + } + } + + if (!(((bbSize - 1) >> shifts) <= 65535)) + { + Panic(); + } + } + + /*-- + Now scan this big bucket so as to synthesise the + sorted order for small buckets [t, ss] for all t != ss. + --*/ + for (j = 0; j <= 255; j++) + { + copy[j] = ftab[(j << 8) + ss] & CLEARMASK; + } + + for (j = ftab[ss << 8] & CLEARMASK; j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) + { + c1 = block[zptr[j]]; + if (!bigDone[c1]) + { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for (j = 0; j <= 255; j++) + { + ftab[(j << 8) + ss] |= SETMASK; + } + } + } + } + + private void RandomiseBlock() + { + int i; + var rNToGo = 0; + var rTPos = 0; + for (i = 0; i < 256; i++) + { + inUse[i] = false; + } + + for (i = 0; i <= last; i++) + { + if (rNToGo == 0) + { + rNToGo = BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + block[i + 1] ^= (byte)((rNToGo == 1) ? 1 : 0); + // handle 16 bit signed numbers + block[i + 1] &= 0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void DoReversibleTransformation() + { + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + MainSort(); + + if (workDone > workLimit && firstAttempt) + { + RandomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + MainSort(); + } + + origPtr = -1; + for (var i = 0; i <= last; i++) + { + if (zptr[i] == 0) + { + origPtr = i; + break; + } + } + + if (origPtr == -1) + { + Panic(); + } + } + + private bool FullGtU(int i1, int i2) + { + int k; + byte c1, c2; + int s1, s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + k = last + 1; + + do + { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + if (i1 > last) + { + i1 -= last; + i1--; + } + if (i2 > last) + { + i2 -= last; + i2--; + } + + k -= 4; + ++workDone; + } while (k >= 0); + + return false; + } + + private void AllocateCompressStructures() + { + var n = BZip2Constants.BaseBlockSize * blockSize100k; + block = new byte[(n + 1 + BZip2Constants.OvershootBytes)]; + quadrant = new int[(n + BZip2Constants.OvershootBytes)]; + zptr = new int[n]; + ftab = new int[65537]; + + if (block == null || quadrant == null || zptr == null || ftab == null) + { + // int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + // compressOutOfMemory ( totalDraw, n ); + } + + /* + The back end needs a place to store the MTF values + whilst it calculates the coding tables. We could + put them in the zptr array. However, these values + will fit in a short, so we overlay szptr at the + start of zptr, in the hope of reducing the number + of cache misses induced by the multiple traversals + of the MTF values when calculating coding tables. + Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + szptr = new short[2 * n]; + } + + private void GenerateMTFValues() + { + var yy = new char[256]; + int i, j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + MakeMaps(); + EOB = nInUse + 1; + + for (i = 0; i <= EOB; i++) + { + mtfFreq[i] = 0; + } + + wr = 0; + zPend = 0; + for (i = 0; i < nInUse; i++) + { + yy[i] = (char)i; + } + + for (i = 0; i <= last; i++) + { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while (ll_i != tmp) + { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + yy[0] = tmp; + + if (j == 0) + { + zPend++; + } + else + { + if (zPend > 0) + { + zPend--; + while (true) + { + switch (zPend % 2) + { + case 0: + szptr[wr] = BZip2Constants.RunA; + wr++; + mtfFreq[BZip2Constants.RunA]++; + break; + + case 1: + szptr[wr] = BZip2Constants.RunB; + wr++; + mtfFreq[BZip2Constants.RunB]++; + break; + } + if (zPend < 2) + { + break; + } + zPend = (zPend - 2) / 2; + } + zPend = 0; + } + szptr[wr] = (short)(j + 1); + wr++; + mtfFreq[j + 1]++; + } + } + + if (zPend > 0) + { + zPend--; + while (true) + { + switch (zPend % 2) + { + case 0: + szptr[wr] = BZip2Constants.RunA; + wr++; + mtfFreq[BZip2Constants.RunA]++; + break; + + case 1: + szptr[wr] = BZip2Constants.RunB; + wr++; + mtfFreq[BZip2Constants.RunB]++; + break; + } + if (zPend < 2) + { + break; + } + zPend = (zPend - 2) / 2; + } + } + + szptr[wr] = (short)EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } + + private static void Panic() + { + throw new BZip2Exception("BZip2 output stream panic"); + } + + private static void HbMakeCodeLengths(char[] len, int[] freq, int alphaSize, int maxLen) + { + /*-- + Nodes and heap entries run from 1. Entry 0 + for both the heap and nodes is a sentinel. + --*/ + int nNodes, nHeap, n1, n2, j, k; + bool tooLong; + + var heap = new int[BZip2Constants.MaximumAlphaSize + 2]; + var weight = new int[BZip2Constants.MaximumAlphaSize * 2]; + var parent = new int[BZip2Constants.MaximumAlphaSize * 2]; + + for (var i = 0; i < alphaSize; ++i) + { + weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; + } + + while (true) + { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for (var i = 1; i <= alphaSize; ++i) + { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + var zz = nHeap; + var tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + if (!(nHeap < (BZip2Constants.MaximumAlphaSize + 2))) + { + Panic(); + } + + while (nHeap > 1) + { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + var zz = 1; + var tmp = heap[zz]; + int yy; + while (true) + { + yy = zz << 1; + if (yy > nHeap) + { + break; + } + if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) + { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) + { + break; + } + + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + + zz = 1; + tmp = heap[zz]; + while (true) + { + yy = zz << 1; + if (yy > nHeap) + { + break; + } + if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) + { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) + { + break; + } + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = (int)((weight[n1] & 0xffffff00) + (weight[n2] & 0xffffff00)) | + 1 + (((weight[n1] & 0x000000ff) > (weight[n2] & 0x000000ff)) ? (weight[n1] & 0x000000ff) : (weight[n2] & 0x000000ff)); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + if (!(nNodes < (BZip2Constants.MaximumAlphaSize * 2))) + { + Panic(); + } + + tooLong = false; + for (var i = 1; i <= alphaSize; ++i) + { + j = 0; + k = i; + while (parent[k] >= 0) + { + k = parent[k]; + j++; + } + len[i - 1] = (char)j; + tooLong |= j > maxLen; + } + + if (!tooLong) + { + break; + } + + for (var i = 1; i < alphaSize; ++i) + { + j = weight[i] >> 8; + j = 1 + (j / 2); + weight[i] = j << 8; + } + } + } + + private static void HbAssignCodes(int[] code, char[] length, int minLen, int maxLen, int alphaSize) + { + var vec = 0; + for (var n = minLen; n <= maxLen; ++n) + { + for (var i = 0; i < alphaSize; ++i) + { + if (length[i] == n) + { + code[i] = vec; + ++vec; + } + } + vec <<= 1; + } + } + + private static byte Med3(byte a, byte b, byte c) + { + byte t; + if (a > b) + { + t = a; + a = b; + b = t; + } + if (b > c) + { + t = b; + b = c; + } + if (a > b) + { + b = a; + } + return b; + } + + private struct StackElement + { + public int ll; + public int hh; + public int dd; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs index 56ffcfe5a..a39586100 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs @@ -1,163 +1,162 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum +namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum; + +/// +/// Computes Adler32 checksum for a stream of data. An Adler32 +/// checksum is not as reliable as a CRC32 checksum, but a lot faster to +/// compute. +/// +/// The specification for Adler32 may be found in RFC 1950. +/// ZLIB Compressed Data Format Specification version 3.3) +/// +/// +/// From that document: +/// +/// "ADLER32 (Adler-32 checksum) +/// This contains a checksum value of the uncompressed data +/// (excluding any dictionary data) computed according to Adler-32 +/// algorithm. This algorithm is a 32-bit extension and improvement +/// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 +/// standard. +/// +/// Adler-32 is composed of two sums accumulated per byte: s1 is +/// the sum of all bytes, s2 is the sum of all s1 values. Both sums +/// are done modulo 65521. s1 is initialized to 1, s2 to zero. The +/// Adler-32 checksum is stored as s2*65536 + s1 in most- +/// significant-byte first (network) order." +/// +/// "8.2. The Adler-32 algorithm +/// +/// The Adler-32 algorithm is much faster than the CRC32 algorithm yet +/// still provides an extremely low probability of undetected errors. +/// +/// The modulo on unsigned long accumulators can be delayed for 5552 +/// bytes, so the modulo operation time is negligible. If the bytes +/// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position +/// and order sensitive, unlike the first sum, which is just a +/// checksum. That 65521 is prime is important to avoid a possible +/// large class of two-byte errors that leave the check unchanged. +/// (The Fletcher checksum uses 255, which is not prime and which also +/// makes the Fletcher check insensitive to single byte changes 0 - +/// 255.) +/// +/// The sum s1 is initialized to 1 instead of zero to make the length +/// of the sequence part of s2, so that the length does not have to be +/// checked separately. (Any sequence of zeroes has a Fletcher +/// checksum of zero.)" +/// +/// +/// +public sealed class Adler32 : IChecksum { - /// - /// Computes Adler32 checksum for a stream of data. An Adler32 - /// checksum is not as reliable as a CRC32 checksum, but a lot faster to - /// compute. - /// - /// The specification for Adler32 may be found in RFC 1950. - /// ZLIB Compressed Data Format Specification version 3.3) - /// - /// - /// From that document: - /// - /// "ADLER32 (Adler-32 checksum) - /// This contains a checksum value of the uncompressed data - /// (excluding any dictionary data) computed according to Adler-32 - /// algorithm. This algorithm is a 32-bit extension and improvement - /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 - /// standard. - /// - /// Adler-32 is composed of two sums accumulated per byte: s1 is - /// the sum of all bytes, s2 is the sum of all s1 values. Both sums - /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The - /// Adler-32 checksum is stored as s2*65536 + s1 in most- - /// significant-byte first (network) order." - /// - /// "8.2. The Adler-32 algorithm - /// - /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet - /// still provides an extremely low probability of undetected errors. - /// - /// The modulo on unsigned long accumulators can be delayed for 5552 - /// bytes, so the modulo operation time is negligible. If the bytes - /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position - /// and order sensitive, unlike the first sum, which is just a - /// checksum. That 65521 is prime is important to avoid a possible - /// large class of two-byte errors that leave the check unchanged. - /// (The Fletcher checksum uses 255, which is not prime and which also - /// makes the Fletcher check insensitive to single byte changes 0 - - /// 255.) - /// - /// The sum s1 is initialized to 1 instead of zero to make the length - /// of the sequence part of s2, so that the length does not have to be - /// checked separately. (Any sequence of zeroes has a Fletcher - /// checksum of zero.)" - /// - /// - /// - public sealed class Adler32 : IChecksum - { - #region Instance Fields + #region Instance Fields - /// - /// largest prime smaller than 65536 - /// - private static readonly uint BASE = 65521; + /// + /// largest prime smaller than 65536 + /// + private static readonly uint BASE = 65521; - /// - /// The CRC data checksum so far. - /// - private uint checkValue; + /// + /// The CRC data checksum so far. + /// + private uint checkValue; - #endregion Instance Fields + #endregion Instance Fields - /// - /// Initialise a default instance of - /// - public Adler32() - { - Reset(); - } + /// + /// Initialise a default instance of + /// + public Adler32() + { + Reset(); + } - /// - /// Resets the Adler32 data checksum as if no update was ever called. - /// - public void Reset() - { - checkValue = 1; - } + /// + /// Resets the Adler32 data checksum as if no update was ever called. + /// + public void Reset() + { + checkValue = 1; + } - /// - /// Returns the Adler32 data checksum computed so far. - /// - public long Value - { - get - { - return checkValue; - } - } + /// + /// Returns the Adler32 data checksum computed so far. + /// + public long Value + { + get + { + return checkValue; + } + } - /// - /// Updates the checksum with the byte b. - /// - /// - /// The data value to add. The high byte of the int is ignored. - /// - public void Update(int bval) - { - // We could make a length 1 byte array and call update again, but I - // would rather not have that overhead - uint s1 = checkValue & 0xFFFF; - uint s2 = checkValue >> 16; + /// + /// Updates the checksum with the byte b. + /// + /// + /// The data value to add. The high byte of the int is ignored. + /// + public void Update(int bval) + { + // We could make a length 1 byte array and call update again, but I + // would rather not have that overhead + var s1 = checkValue & 0xFFFF; + var s2 = checkValue >> 16; - s1 = (s1 + ((uint)bval & 0xFF)) % BASE; - s2 = (s1 + s2) % BASE; + s1 = (s1 + ((uint)bval & 0xFF)) % BASE; + s2 = (s1 + s2) % BASE; - checkValue = (s2 << 16) + s1; - } + checkValue = (s2 << 16) + s1; + } - /// - /// Updates the Adler32 data checksum with the bytes taken from - /// a block of data. - /// - /// Contains the data to update the checksum with. - public void Update(byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + /// + /// Updates the Adler32 data checksum with the bytes taken from + /// a block of data. + /// + /// Contains the data to update the checksum with. + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - Update(new ArraySegment(buffer, 0, buffer.Length)); - } + Update(new ArraySegment(buffer, 0, buffer.Length)); + } - /// - /// Update Adler32 data checksum based on a portion of a block of data - /// - /// - /// The chunk of data to add - /// - public void Update(ArraySegment segment) - { - //(By Per Bothner) - uint s1 = checkValue & 0xFFFF; - uint s2 = checkValue >> 16; - var count = segment.Count; - var offset = segment.Offset; - while (count > 0) - { - // We can defer the modulo operation: - // s1 maximally grows from 65521 to 65521 + 255 * 3800 - // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 - int n = 3800; - if (n > count) - { - n = count; - } - count -= n; - while (--n >= 0) - { - s1 = s1 + (uint)(segment.Array[offset++] & 0xff); - s2 = s2 + s1; - } - s1 %= BASE; - s2 %= BASE; - } - checkValue = (s2 << 16) | s1; - } - } + /// + /// Update Adler32 data checksum based on a portion of a block of data + /// + /// + /// The chunk of data to add + /// + public void Update(ArraySegment segment) + { + //(By Per Bothner) + var s1 = checkValue & 0xFFFF; + var s2 = checkValue >> 16; + var count = segment.Count; + var offset = segment.Offset; + while (count > 0) + { + // We can defer the modulo operation: + // s1 maximally grows from 65521 to 65521 + 255 * 3800 + // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 + var n = 3800; + if (n > count) + { + n = count; + } + count -= n; + while (--n >= 0) + { + s1 += (uint)(segment.Array[offset++] & 0xff); + s2 += s1; + } + s1 %= BASE; + s2 %= BASE; + } + checkValue = (s2 << 16) | s1; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs index b5320338f..1d4063900 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs @@ -1,171 +1,170 @@ using System; using System.Runtime.CompilerServices; -namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum +namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum; + +/// +/// CRC-32 with unreversed data and reversed output +/// +/// +/// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: +/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. +/// +/// Polynomials over GF(2) are represented in binary, one bit per coefficient, +/// with the lowest powers in the most significant bit. Then adding polynomials +/// is just exclusive-or, and multiplying a polynomial by x is a right shift by +/// one. If we call the above polynomial p, and represent a byte as the +/// polynomial q, also with the lowest power in the most significant bit (so the +/// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, +/// where a mod b means the remainder after dividing a by b. +/// +/// This calculation is done using the shift-register method of multiplying and +/// taking the remainder. The register is initialized to zero, and for each +/// incoming bit, x^32 is added mod p to the register if the bit is a one (where +/// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by +/// x (which is shifting right by one and adding x^32 mod p if the bit shifted +/// out is a one). We start with the highest power (least significant bit) of +/// q and repeat for all eight bits of q. +/// +/// This implementation uses sixteen lookup tables stored in one linear array +/// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8 +/// algorithm described in this Intel white paper: +/// +/// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf +/// +/// The first lookup table is simply the CRC of all possible eight bit values. +/// Each successive lookup table is derived from the original table generated +/// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs +/// together will produce the same output as a byte-by-byte CRC loop with +/// fewer arithmetic and bit manipulation operations, at the cost of increased +/// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table, +/// which is still small enough to fit in most processors' L1 cache.) +/// +public sealed class BZip2Crc : IChecksum { - /// - /// CRC-32 with unreversed data and reversed output - /// - /// - /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: - /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. - /// - /// Polynomials over GF(2) are represented in binary, one bit per coefficient, - /// with the lowest powers in the most significant bit. Then adding polynomials - /// is just exclusive-or, and multiplying a polynomial by x is a right shift by - /// one. If we call the above polynomial p, and represent a byte as the - /// polynomial q, also with the lowest power in the most significant bit (so the - /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, - /// where a mod b means the remainder after dividing a by b. - /// - /// This calculation is done using the shift-register method of multiplying and - /// taking the remainder. The register is initialized to zero, and for each - /// incoming bit, x^32 is added mod p to the register if the bit is a one (where - /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by - /// x (which is shifting right by one and adding x^32 mod p if the bit shifted - /// out is a one). We start with the highest power (least significant bit) of - /// q and repeat for all eight bits of q. - /// - /// This implementation uses sixteen lookup tables stored in one linear array - /// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8 - /// algorithm described in this Intel white paper: - /// - /// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf - /// - /// The first lookup table is simply the CRC of all possible eight bit values. - /// Each successive lookup table is derived from the original table generated - /// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs - /// together will produce the same output as a byte-by-byte CRC loop with - /// fewer arithmetic and bit manipulation operations, at the cost of increased - /// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table, - /// which is still small enough to fit in most processors' L1 cache.) - /// - public sealed class BZip2Crc : IChecksum - { - #region Instance Fields - - private const uint crcInit = 0xFFFFFFFF; - //const uint crcXor = 0x00000000; - - private static readonly uint[] crcTable = CrcUtilities.GenerateSlicingLookupTable(0x04C11DB7, isReversed: false); - - /// - /// The CRC data checksum so far. - /// - private uint checkValue; - - #endregion Instance Fields - - /// - /// Initialise a default instance of - /// - public BZip2Crc() - { - Reset(); - } - - /// - /// Resets the CRC data checksum as if no update was ever called. - /// - public void Reset() - { - checkValue = crcInit; - } - - /// - /// Returns the CRC data checksum computed so far. - /// - /// Reversed Out = true - public long Value - { - get - { - // Technically, the output should be: - //return (long)(~checkValue ^ crcXor); - // but x ^ 0 = x, so there is no point in adding - // the XOR operation - return (long)(~checkValue); - } - } - - /// - /// Updates the checksum with the int bval. - /// - /// - /// the byte is taken as the lower 8 bits of bval - /// - /// Reversed Data = false - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(int bval) - { - checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8)); - } - - /// - /// Updates the CRC data checksum with the bytes taken from - /// a block of data. - /// - /// Contains the data to update the CRC with. - public void Update(byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - Update(buffer, 0, buffer.Length); - } - - /// - /// Update CRC data checksum based on a portion of a block of data - /// - /// - /// The chunk of data to add - /// - public void Update(ArraySegment segment) - { - Update(segment.Array, segment.Offset, segment.Count); - } - - /// - /// Internal helper function for updating a block of data using slicing. - /// - /// The array containing the data to add - /// Range start for (inclusive) - /// The number of bytes to checksum starting from - private void Update(byte[] data, int offset, int count) - { - int remainder = count % CrcUtilities.SlicingDegree; - int end = offset + count - remainder; - - while (offset != end) - { - checkValue = CrcUtilities.UpdateDataForNormalPoly(data, offset, crcTable, checkValue); - offset += CrcUtilities.SlicingDegree; - } - - if (remainder != 0) - { - SlowUpdateLoop(data, offset, end + remainder); - } - } - - /// - /// A non-inlined function for updating data that doesn't fit in a 16-byte - /// block. We don't expect to enter this function most of the time, and when - /// we do we're not here for long, so disabling inlining here improves - /// performance overall. - /// - /// The array containing the data to add - /// Range start for (inclusive) - /// Range end for (exclusive) - [MethodImpl(MethodImplOptions.NoInlining)] - private void SlowUpdateLoop(byte[] data, int offset, int end) - { - while (offset != end) - { - Update(data[offset++]); - } - } - } + #region Instance Fields + + private const uint crcInit = 0xFFFFFFFF; + //const uint crcXor = 0x00000000; + + private static readonly uint[] crcTable = CrcUtilities.GenerateSlicingLookupTable(0x04C11DB7, isReversed: false); + + /// + /// The CRC data checksum so far. + /// + private uint checkValue; + + #endregion Instance Fields + + /// + /// Initialise a default instance of + /// + public BZip2Crc() + { + Reset(); + } + + /// + /// Resets the CRC data checksum as if no update was ever called. + /// + public void Reset() + { + checkValue = crcInit; + } + + /// + /// Returns the CRC data checksum computed so far. + /// + /// Reversed Out = true + public long Value + { + get + { + // Technically, the output should be: + //return (long)(~checkValue ^ crcXor); + // but x ^ 0 = x, so there is no point in adding + // the XOR operation + return ~checkValue; + } + } + + /// + /// Updates the checksum with the int bval. + /// + /// + /// the byte is taken as the lower 8 bits of bval + /// + /// Reversed Data = false + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(int bval) + { + checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8)); + } + + /// + /// Updates the CRC data checksum with the bytes taken from + /// a block of data. + /// + /// Contains the data to update the CRC with. + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + Update(buffer, 0, buffer.Length); + } + + /// + /// Update CRC data checksum based on a portion of a block of data + /// + /// + /// The chunk of data to add + /// + public void Update(ArraySegment segment) + { + Update(segment.Array, segment.Offset, segment.Count); + } + + /// + /// Internal helper function for updating a block of data using slicing. + /// + /// The array containing the data to add + /// Range start for (inclusive) + /// The number of bytes to checksum starting from + private void Update(byte[] data, int offset, int count) + { + var remainder = count % CrcUtilities.SlicingDegree; + var end = offset + count - remainder; + + while (offset != end) + { + checkValue = CrcUtilities.UpdateDataForNormalPoly(data, offset, crcTable, checkValue); + offset += CrcUtilities.SlicingDegree; + } + + if (remainder != 0) + { + SlowUpdateLoop(data, offset, end + remainder); + } + } + + /// + /// A non-inlined function for updating data that doesn't fit in a 16-byte + /// block. We don't expect to enter this function most of the time, and when + /// we do we're not here for long, so disabling inlining here improves + /// performance overall. + /// + /// The array containing the data to add + /// Range start for (inclusive) + /// Range end for (exclusive) + [MethodImpl(MethodImplOptions.NoInlining)] + private void SlowUpdateLoop(byte[] data, int offset, int end) + { + while (offset != end) + { + Update(data[offset++]); + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs index 2f57606a7..f167bc770 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs @@ -1,173 +1,172 @@ using System; using System.Runtime.CompilerServices; -namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum +namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum; + +/// +/// CRC-32 with reversed data and unreversed output +/// +/// +/// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: +/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. +/// +/// Polynomials over GF(2) are represented in binary, one bit per coefficient, +/// with the lowest powers in the most significant bit. Then adding polynomials +/// is just exclusive-or, and multiplying a polynomial by x is a right shift by +/// one. If we call the above polynomial p, and represent a byte as the +/// polynomial q, also with the lowest power in the most significant bit (so the +/// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, +/// where a mod b means the remainder after dividing a by b. +/// +/// This calculation is done using the shift-register method of multiplying and +/// taking the remainder. The register is initialized to zero, and for each +/// incoming bit, x^32 is added mod p to the register if the bit is a one (where +/// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by +/// x (which is shifting right by one and adding x^32 mod p if the bit shifted +/// out is a one). We start with the highest power (least significant bit) of +/// q and repeat for all eight bits of q. +/// +/// This implementation uses sixteen lookup tables stored in one linear array +/// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8 +/// algorithm described in this Intel white paper: +/// +/// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf +/// +/// The first lookup table is simply the CRC of all possible eight bit values. +/// Each successive lookup table is derived from the original table generated +/// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs +/// together will produce the same output as a byte-by-byte CRC loop with +/// fewer arithmetic and bit manipulation operations, at the cost of increased +/// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table, +/// which is still small enough to fit in most processors' L1 cache.) +/// +public sealed class Crc32 : IChecksum { - /// - /// CRC-32 with reversed data and unreversed output - /// - /// - /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: - /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. - /// - /// Polynomials over GF(2) are represented in binary, one bit per coefficient, - /// with the lowest powers in the most significant bit. Then adding polynomials - /// is just exclusive-or, and multiplying a polynomial by x is a right shift by - /// one. If we call the above polynomial p, and represent a byte as the - /// polynomial q, also with the lowest power in the most significant bit (so the - /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, - /// where a mod b means the remainder after dividing a by b. - /// - /// This calculation is done using the shift-register method of multiplying and - /// taking the remainder. The register is initialized to zero, and for each - /// incoming bit, x^32 is added mod p to the register if the bit is a one (where - /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by - /// x (which is shifting right by one and adding x^32 mod p if the bit shifted - /// out is a one). We start with the highest power (least significant bit) of - /// q and repeat for all eight bits of q. - /// - /// This implementation uses sixteen lookup tables stored in one linear array - /// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8 - /// algorithm described in this Intel white paper: - /// - /// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf - /// - /// The first lookup table is simply the CRC of all possible eight bit values. - /// Each successive lookup table is derived from the original table generated - /// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs - /// together will produce the same output as a byte-by-byte CRC loop with - /// fewer arithmetic and bit manipulation operations, at the cost of increased - /// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table, - /// which is still small enough to fit in most processors' L1 cache.) - /// - public sealed class Crc32 : IChecksum - { - #region Instance Fields - - private static readonly uint crcInit = 0xFFFFFFFF; - private static readonly uint crcXor = 0xFFFFFFFF; - - private static readonly uint[] crcTable = CrcUtilities.GenerateSlicingLookupTable(0xEDB88320, isReversed: true); - - /// - /// The CRC data checksum so far. - /// - private uint checkValue; - - #endregion Instance Fields - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static uint ComputeCrc32(uint oldCrc, byte bval) - { - return (uint)(Crc32.crcTable[(oldCrc ^ bval) & 0xFF] ^ (oldCrc >> 8)); - } - - /// - /// Initialise a default instance of - /// - public Crc32() - { - Reset(); - } - - /// - /// Resets the CRC data checksum as if no update was ever called. - /// - public void Reset() - { - checkValue = crcInit; - } - - /// - /// Returns the CRC data checksum computed so far. - /// - /// Reversed Out = false - public long Value - { - get - { - return (long)(checkValue ^ crcXor); - } - } - - /// - /// Updates the checksum with the int bval. - /// - /// - /// the byte is taken as the lower 8 bits of bval - /// - /// Reversed Data = true - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(int bval) - { - checkValue = unchecked(crcTable[(checkValue ^ bval) & 0xFF] ^ (checkValue >> 8)); - } - - /// - /// Updates the CRC data checksum with the bytes taken from - /// a block of data. - /// - /// Contains the data to update the CRC with. - public void Update(byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - Update(buffer, 0, buffer.Length); - } - - /// - /// Update CRC data checksum based on a portion of a block of data - /// - /// - /// The chunk of data to add - /// - public void Update(ArraySegment segment) - { - Update(segment.Array, segment.Offset, segment.Count); - } - - /// - /// Internal helper function for updating a block of data using slicing. - /// - /// The array containing the data to add - /// Range start for (inclusive) - /// The number of bytes to checksum starting from - private void Update(byte[] data, int offset, int count) - { - int remainder = count % CrcUtilities.SlicingDegree; - int end = offset + count - remainder; - - while (offset != end) - { - checkValue = CrcUtilities.UpdateDataForReversedPoly(data, offset, crcTable, checkValue); - offset += CrcUtilities.SlicingDegree; - } - - if (remainder != 0) - { - SlowUpdateLoop(data, offset, end + remainder); - } - } - - /// - /// A non-inlined function for updating data that doesn't fit in a 16-byte - /// block. We don't expect to enter this function most of the time, and when - /// we do we're not here for long, so disabling inlining here improves - /// performance overall. - /// - /// The array containing the data to add - /// Range start for (inclusive) - /// Range end for (exclusive) - [MethodImpl(MethodImplOptions.NoInlining)] - private void SlowUpdateLoop(byte[] data, int offset, int end) - { - while (offset != end) - { - Update(data[offset++]); - } - } - } + #region Instance Fields + + private static readonly uint crcInit = 0xFFFFFFFF; + private static readonly uint crcXor = 0xFFFFFFFF; + + private static readonly uint[] crcTable = CrcUtilities.GenerateSlicingLookupTable(0xEDB88320, isReversed: true); + + /// + /// The CRC data checksum so far. + /// + private uint checkValue; + + #endregion Instance Fields + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint ComputeCrc32(uint oldCrc, byte bval) + { + return Crc32.crcTable[(oldCrc ^ bval) & 0xFF] ^ (oldCrc >> 8); + } + + /// + /// Initialise a default instance of + /// + public Crc32() + { + Reset(); + } + + /// + /// Resets the CRC data checksum as if no update was ever called. + /// + public void Reset() + { + checkValue = crcInit; + } + + /// + /// Returns the CRC data checksum computed so far. + /// + /// Reversed Out = false + public long Value + { + get + { + return checkValue ^ crcXor; + } + } + + /// + /// Updates the checksum with the int bval. + /// + /// + /// the byte is taken as the lower 8 bits of bval + /// + /// Reversed Data = true + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(int bval) + { + checkValue = unchecked(crcTable[(checkValue ^ bval) & 0xFF] ^ (checkValue >> 8)); + } + + /// + /// Updates the CRC data checksum with the bytes taken from + /// a block of data. + /// + /// Contains the data to update the CRC with. + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + Update(buffer, 0, buffer.Length); + } + + /// + /// Update CRC data checksum based on a portion of a block of data + /// + /// + /// The chunk of data to add + /// + public void Update(ArraySegment segment) + { + Update(segment.Array, segment.Offset, segment.Count); + } + + /// + /// Internal helper function for updating a block of data using slicing. + /// + /// The array containing the data to add + /// Range start for (inclusive) + /// The number of bytes to checksum starting from + private void Update(byte[] data, int offset, int count) + { + var remainder = count % CrcUtilities.SlicingDegree; + var end = offset + count - remainder; + + while (offset != end) + { + checkValue = CrcUtilities.UpdateDataForReversedPoly(data, offset, crcTable, checkValue); + offset += CrcUtilities.SlicingDegree; + } + + if (remainder != 0) + { + SlowUpdateLoop(data, offset, end + remainder); + } + } + + /// + /// A non-inlined function for updating data that doesn't fit in a 16-byte + /// block. We don't expect to enter this function most of the time, and when + /// we do we're not here for long, so disabling inlining here improves + /// performance overall. + /// + /// The array containing the data to add + /// Range start for (inclusive) + /// Range end for (exclusive) + [MethodImpl(MethodImplOptions.NoInlining)] + private void SlowUpdateLoop(byte[] data, int offset, int end) + { + while (offset != end) + { + Update(data[offset++]); + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs index ec45253e5..bbea95186 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs @@ -1,158 +1,148 @@ -using System.Runtime.CompilerServices; +namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum; -namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum +internal static class CrcUtilities { - internal static class CrcUtilities - { - /// - /// The number of slicing lookup tables to generate. - /// - internal const int SlicingDegree = 16; + /// + /// The number of slicing lookup tables to generate. + /// + internal const int SlicingDegree = 16; - /// - /// Generates multiple CRC lookup tables for a given polynomial, stored - /// in a linear array of uints. The first block (i.e. the first 256 - /// elements) is the same as the byte-by-byte CRC lookup table. - /// - /// The generating CRC polynomial - /// Whether the polynomial is in reversed bit order - /// A linear array of 256 * elements - /// - /// This table could also be generated as a rectangular array, but the - /// JIT compiler generates slower code than if we use a linear array. - /// Known issue, see: https://github.com/dotnet/runtime/issues/30275 - /// - internal static uint[] GenerateSlicingLookupTable(uint polynomial, bool isReversed) - { - var table = new uint[256 * SlicingDegree]; - uint one = isReversed ? 1 : (1U << 31); + /// + /// Generates multiple CRC lookup tables for a given polynomial, stored + /// in a linear array of uints. The first block (i.e. the first 256 + /// elements) is the same as the byte-by-byte CRC lookup table. + /// + /// The generating CRC polynomial + /// Whether the polynomial is in reversed bit order + /// A linear array of 256 * elements + /// + /// This table could also be generated as a rectangular array, but the + /// JIT compiler generates slower code than if we use a linear array. + /// Known issue, see: https://github.com/dotnet/runtime/issues/30275 + /// + internal static uint[] GenerateSlicingLookupTable(uint polynomial, bool isReversed) + { + var table = new uint[256 * SlicingDegree]; + var one = isReversed ? 1 : (1U << 31); - for (int i = 0; i < 256; i++) - { - uint res = (uint)(isReversed ? i : i << 24); - for (int j = 0; j < SlicingDegree; j++) - { - for (int k = 0; k < 8; k++) - { - if (isReversed) - { - res = (res & one) == 1 ? polynomial ^ (res >> 1) : res >> 1; - } - else - { - res = (res & one) != 0 ? polynomial ^ (res << 1) : res << 1; - } - } + for (var i = 0; i < 256; i++) + { + var res = (uint)(isReversed ? i : i << 24); + for (var j = 0; j < SlicingDegree; j++) + { + for (var k = 0; k < 8; k++) + { + res = isReversed ? (res & one) == 1 ? polynomial ^ (res >> 1) : res >> 1 : (res & one) != 0 ? polynomial ^ (res << 1) : res << 1; + } - table[(256 * j) + i] = res; - } - } + table[(256 * j) + i] = res; + } + } - return table; - } + return table; + } - /// - /// Mixes the first four bytes of input with - /// using normal ordering before calling . - /// - /// Array of data to checksum - /// Offset to start reading from - /// The table to use for slicing-by-16 lookup - /// Checksum state before this update call - /// A new unfinalized checksum value - /// - /// - /// Assumes input[offset]..input[offset + 15] are valid array indexes. - /// For performance reasons, this must be checked by the caller. - /// - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static uint UpdateDataForNormalPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) - { - byte x1 = (byte)((byte)(checkValue >> 24) ^ input[offset]); - byte x2 = (byte)((byte)(checkValue >> 16) ^ input[offset + 1]); - byte x3 = (byte)((byte)(checkValue >> 8) ^ input[offset + 2]); - byte x4 = (byte)((byte)checkValue ^ input[offset + 3]); + /// + /// Mixes the first four bytes of input with + /// using normal ordering before calling . + /// + /// Array of data to checksum + /// Offset to start reading from + /// The table to use for slicing-by-16 lookup + /// Checksum state before this update call + /// A new unfinalized checksum value + /// + /// + /// Assumes input[offset]..input[offset + 15] are valid array indexes. + /// For performance reasons, this must be checked by the caller. + /// + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint UpdateDataForNormalPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) + { + var x1 = (byte)((byte)(checkValue >> 24) ^ input[offset]); + var x2 = (byte)((byte)(checkValue >> 16) ^ input[offset + 1]); + var x3 = (byte)((byte)(checkValue >> 8) ^ input[offset + 2]); + var x4 = (byte)((byte)checkValue ^ input[offset + 3]); - return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); - } + return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); + } - /// - /// Mixes the first four bytes of input with - /// using reflected ordering before calling . - /// - /// Array of data to checksum - /// Offset to start reading from - /// The table to use for slicing-by-16 lookup - /// Checksum state before this update call - /// A new unfinalized checksum value - /// - /// - /// Assumes input[offset]..input[offset + 15] are valid array indexes. - /// For performance reasons, this must be checked by the caller. - /// - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static uint UpdateDataForReversedPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) - { - byte x1 = (byte)((byte)checkValue ^ input[offset]); - byte x2 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 1]); - byte x3 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 2]); - byte x4 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 3]); + /// + /// Mixes the first four bytes of input with + /// using reflected ordering before calling . + /// + /// Array of data to checksum + /// Offset to start reading from + /// The table to use for slicing-by-16 lookup + /// Checksum state before this update call + /// A new unfinalized checksum value + /// + /// + /// Assumes input[offset]..input[offset + 15] are valid array indexes. + /// For performance reasons, this must be checked by the caller. + /// + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint UpdateDataForReversedPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) + { + var x1 = (byte)((byte)checkValue ^ input[offset]); + var x2 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 1]); + var x3 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 2]); + var x4 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 3]); - return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); - } + return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); + } - /// - /// A shared method for updating an unfinalized CRC checksum using slicing-by-16. - /// - /// Array of data to checksum - /// Offset to start reading from - /// The table to use for slicing-by-16 lookup - /// First byte of input after mixing with the old CRC - /// Second byte of input after mixing with the old CRC - /// Third byte of input after mixing with the old CRC - /// Fourth byte of input after mixing with the old CRC - /// A new unfinalized checksum value - /// - /// - /// Even though the first four bytes of input are fed in as arguments, - /// should be the same value passed to this - /// function's caller (either or - /// ). This method will get inlined - /// into both functions, so using the same offset produces faster code. - /// - /// - /// Because most processors running C# have some kind of instruction-level - /// parallelism, the order of XOR operations can affect performance. This - /// ordering assumes that the assembly code generated by the just-in-time - /// compiler will emit a bunch of arithmetic operations for checking array - /// bounds. Then it opportunistically XORs a1 and a2 to keep the processor - /// busy while those other parts of the pipeline handle the range check - /// calculations. - /// - /// - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint UpdateDataCommon(byte[] input, int offset, uint[] crcTable, byte x1, byte x2, byte x3, byte x4) - { - uint result; - uint a1 = crcTable[x1 + 3840] ^ crcTable[x2 + 3584]; - uint a2 = crcTable[x3 + 3328] ^ crcTable[x4 + 3072]; + /// + /// A shared method for updating an unfinalized CRC checksum using slicing-by-16. + /// + /// Array of data to checksum + /// Offset to start reading from + /// The table to use for slicing-by-16 lookup + /// First byte of input after mixing with the old CRC + /// Second byte of input after mixing with the old CRC + /// Third byte of input after mixing with the old CRC + /// Fourth byte of input after mixing with the old CRC + /// A new unfinalized checksum value + /// + /// + /// Even though the first four bytes of input are fed in as arguments, + /// should be the same value passed to this + /// function's caller (either or + /// ). This method will get inlined + /// into both functions, so using the same offset produces faster code. + /// + /// + /// Because most processors running C# have some kind of instruction-level + /// parallelism, the order of XOR operations can affect performance. This + /// ordering assumes that the assembly code generated by the just-in-time + /// compiler will emit a bunch of arithmetic operations for checking array + /// bounds. Then it opportunistically XORs a1 and a2 to keep the processor + /// busy while those other parts of the pipeline handle the range check + /// calculations. + /// + /// + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint UpdateDataCommon(byte[] input, int offset, uint[] crcTable, byte x1, byte x2, byte x3, byte x4) + { + uint result; + var a1 = crcTable[x1 + 3840] ^ crcTable[x2 + 3584]; + var a2 = crcTable[x3 + 3328] ^ crcTable[x4 + 3072]; - result = crcTable[input[offset + 4] + 2816]; - result ^= crcTable[input[offset + 5] + 2560]; - a1 ^= crcTable[input[offset + 9] + 1536]; - result ^= crcTable[input[offset + 6] + 2304]; - result ^= crcTable[input[offset + 7] + 2048]; - result ^= crcTable[input[offset + 8] + 1792]; - a2 ^= crcTable[input[offset + 13] + 512]; - result ^= crcTable[input[offset + 10] + 1280]; - result ^= crcTable[input[offset + 11] + 1024]; - result ^= crcTable[input[offset + 12] + 768]; - result ^= a1; - result ^= crcTable[input[offset + 14] + 256]; - result ^= crcTable[input[offset + 15]]; - result ^= a2; + result = crcTable[input[offset + 4] + 2816]; + result ^= crcTable[input[offset + 5] + 2560]; + a1 ^= crcTable[input[offset + 9] + 1536]; + result ^= crcTable[input[offset + 6] + 2304]; + result ^= crcTable[input[offset + 7] + 2048]; + result ^= crcTable[input[offset + 8] + 1792]; + a2 ^= crcTable[input[offset + 13] + 512]; + result ^= crcTable[input[offset + 10] + 1280]; + result ^= crcTable[input[offset + 11] + 1024]; + result ^= crcTable[input[offset + 12] + 768]; + result ^= a1; + result ^= crcTable[input[offset + 14] + 256]; + result ^= crcTable[input[offset + 15]]; + result ^= a2; - return result; - } - } + return result; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs index d5ba8eff4..e6baae37b 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs @@ -1,51 +1,50 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum +namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum; + +/// +/// Interface to compute a data checksum used by checked input/output streams. +/// A data checksum can be updated by one byte or with a byte array. After each +/// update the value of the current checksum can be returned by calling +/// getValue. The complete checksum object can also be reset +/// so it can be used again with new data. +/// +public interface IChecksum { - /// - /// Interface to compute a data checksum used by checked input/output streams. - /// A data checksum can be updated by one byte or with a byte array. After each - /// update the value of the current checksum can be returned by calling - /// getValue. The complete checksum object can also be reset - /// so it can be used again with new data. - /// - public interface IChecksum - { - /// - /// Resets the data checksum as if no update was ever called. - /// - void Reset(); + /// + /// Resets the data checksum as if no update was ever called. + /// + void Reset(); - /// - /// Returns the data checksum computed so far. - /// - long Value - { - get; - } + /// + /// Returns the data checksum computed so far. + /// + long Value + { + get; + } - /// - /// Adds one byte to the data checksum. - /// - /// - /// the data value to add. The high byte of the int is ignored. - /// - void Update(int bval); + /// + /// Adds one byte to the data checksum. + /// + /// + /// the data value to add. The high byte of the int is ignored. + /// + void Update(int bval); - /// - /// Updates the data checksum with the bytes taken from the array. - /// - /// - /// buffer an array of bytes - /// - void Update(byte[] buffer); + /// + /// Updates the data checksum with the bytes taken from the array. + /// + /// + /// buffer an array of bytes + /// + void Update(byte[] buffer); - /// - /// Adds the byte array to the data checksum. - /// - /// - /// The chunk of data to add - /// - void Update(ArraySegment segment); - } + /// + /// Adds the byte array to the data checksum. + /// + /// + /// The chunk of data to add + /// + void Update(ArraySegment segment); } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs index 9f9bdc44b..8dce143f2 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs @@ -1,13 +1,10 @@ -using System; +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +internal static class Empty { - internal static class Empty - { - internal static class EmptyArray - { - public static readonly T[] Value = new T[0]; - } - public static T[] Array() => EmptyArray.Value; - } + internal static class EmptyArray + { + public static readonly T[] Value = new T[0]; + } + public static T[] Array() => EmptyArray.Value; } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs index 5c572190e..3f2c05275 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs @@ -1,58 +1,57 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib +namespace MelonLoader.ICSharpCode.SharpZipLib; + +/// +/// SharpZipBaseException is the base exception class for SharpZipLib. +/// All library exceptions are derived from this. +/// +/// NOTE: Not all exceptions thrown will be derived from this class. +/// A variety of other exceptions are possible for example +[Serializable] +public class SharpZipBaseException : Exception { - /// - /// SharpZipBaseException is the base exception class for SharpZipLib. - /// All library exceptions are derived from this. - /// - /// NOTE: Not all exceptions thrown will be derived from this class. - /// A variety of other exceptions are possible for example - [Serializable] - public class SharpZipBaseException : Exception - { - /// - /// Initializes a new instance of the SharpZipBaseException class. - /// - public SharpZipBaseException() - { - } + /// + /// Initializes a new instance of the SharpZipBaseException class. + /// + public SharpZipBaseException() + { + } - /// - /// Initializes a new instance of the SharpZipBaseException class with a specified error message. - /// - /// A message describing the exception. - public SharpZipBaseException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the SharpZipBaseException class with a specified error message. + /// + /// A message describing the exception. + public SharpZipBaseException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the SharpZipBaseException class with a specified - /// error message and a reference to the inner exception that is the cause of this exception. - /// - /// A message describing the exception. - /// The inner exception - public SharpZipBaseException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the SharpZipBaseException class with a specified + /// error message and a reference to the inner exception that is the cause of this exception. + /// + /// A message describing the exception. + /// The inner exception + public SharpZipBaseException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the SharpZipBaseException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected SharpZipBaseException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the SharpZipBaseException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected SharpZipBaseException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs index 02eb7c701..60cb3ede7 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs @@ -1,50 +1,49 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib +namespace MelonLoader.ICSharpCode.SharpZipLib; + +/// +/// Indicates that an error occurred during decoding of a input stream due to corrupt +/// data or (unintentional) library incompatibility. +/// +[Serializable] +public class StreamDecodingException : SharpZipBaseException { - /// - /// Indicates that an error occurred during decoding of a input stream due to corrupt - /// data or (unintentional) library incompatibility. - /// - [Serializable] - public class StreamDecodingException : SharpZipBaseException - { - private const string GenericMessage = "Input stream could not be decoded"; + private const string GenericMessage = "Input stream could not be decoded"; - /// - /// Initializes a new instance of the StreamDecodingException with a generic message - /// - public StreamDecodingException() : base(GenericMessage) { } + /// + /// Initializes a new instance of the StreamDecodingException with a generic message + /// + public StreamDecodingException() : base(GenericMessage) { } - /// - /// Initializes a new instance of the StreamDecodingException class with a specified error message. - /// - /// A message describing the exception. - public StreamDecodingException(string message) : base(message) { } + /// + /// Initializes a new instance of the StreamDecodingException class with a specified error message. + /// + /// A message describing the exception. + public StreamDecodingException(string message) : base(message) { } - /// - /// Initializes a new instance of the StreamDecodingException class with a specified - /// error message and a reference to the inner exception that is the cause of this exception. - /// - /// A message describing the exception. - /// The inner exception - public StreamDecodingException(string message, Exception innerException) : base(message, innerException) { } + /// + /// Initializes a new instance of the StreamDecodingException class with a specified + /// error message and a reference to the inner exception that is the cause of this exception. + /// + /// A message describing the exception. + /// The inner exception + public StreamDecodingException(string message, Exception innerException) : base(message, innerException) { } - /// - /// Initializes a new instance of the StreamDecodingException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected StreamDecodingException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the StreamDecodingException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected StreamDecodingException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs index 58e153bfb..3c1214345 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs @@ -1,49 +1,48 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib +namespace MelonLoader.ICSharpCode.SharpZipLib; + +/// +/// Indicates that the input stream could not decoded due to known library incompability or missing features +/// +[Serializable] +public class StreamUnsupportedException : StreamDecodingException { - /// - /// Indicates that the input stream could not decoded due to known library incompability or missing features - /// - [Serializable] - public class StreamUnsupportedException : StreamDecodingException - { - private const string GenericMessage = "Input stream is in a unsupported format"; + private const string GenericMessage = "Input stream is in a unsupported format"; - /// - /// Initializes a new instance of the StreamUnsupportedException with a generic message - /// - public StreamUnsupportedException() : base(GenericMessage) { } + /// + /// Initializes a new instance of the StreamUnsupportedException with a generic message + /// + public StreamUnsupportedException() : base(GenericMessage) { } - /// - /// Initializes a new instance of the StreamUnsupportedException class with a specified error message. - /// - /// A message describing the exception. - public StreamUnsupportedException(string message) : base(message) { } + /// + /// Initializes a new instance of the StreamUnsupportedException class with a specified error message. + /// + /// A message describing the exception. + public StreamUnsupportedException(string message) : base(message) { } - /// - /// Initializes a new instance of the StreamUnsupportedException class with a specified - /// error message and a reference to the inner exception that is the cause of this exception. - /// - /// A message describing the exception. - /// The inner exception - public StreamUnsupportedException(string message, Exception innerException) : base(message, innerException) { } + /// + /// Initializes a new instance of the StreamUnsupportedException class with a specified + /// error message and a reference to the inner exception that is the cause of this exception. + /// + /// A message describing the exception. + /// The inner exception + public StreamUnsupportedException(string message, Exception innerException) : base(message, innerException) { } - /// - /// Initializes a new instance of the StreamUnsupportedException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected StreamUnsupportedException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the StreamUnsupportedException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected StreamUnsupportedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs index 11f56d3e3..fe7cd0d4b 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs @@ -1,49 +1,48 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib +namespace MelonLoader.ICSharpCode.SharpZipLib; + +/// +/// Indicates that the input stream could not decoded due to the stream ending before enough data had been provided +/// +[Serializable] +public class UnexpectedEndOfStreamException : StreamDecodingException { - /// - /// Indicates that the input stream could not decoded due to the stream ending before enough data had been provided - /// - [Serializable] - public class UnexpectedEndOfStreamException : StreamDecodingException - { - private const string GenericMessage = "Input stream ended unexpectedly"; + private const string GenericMessage = "Input stream ended unexpectedly"; - /// - /// Initializes a new instance of the UnexpectedEndOfStreamException with a generic message - /// - public UnexpectedEndOfStreamException() : base(GenericMessage) { } + /// + /// Initializes a new instance of the UnexpectedEndOfStreamException with a generic message + /// + public UnexpectedEndOfStreamException() : base(GenericMessage) { } - /// - /// Initializes a new instance of the UnexpectedEndOfStreamException class with a specified error message. - /// - /// A message describing the exception. - public UnexpectedEndOfStreamException(string message) : base(message) { } + /// + /// Initializes a new instance of the UnexpectedEndOfStreamException class with a specified error message. + /// + /// A message describing the exception. + public UnexpectedEndOfStreamException(string message) : base(message) { } - /// - /// Initializes a new instance of the UnexpectedEndOfStreamException class with a specified - /// error message and a reference to the inner exception that is the cause of this exception. - /// - /// A message describing the exception. - /// The inner exception - public UnexpectedEndOfStreamException(string message, Exception innerException) : base(message, innerException) { } + /// + /// Initializes a new instance of the UnexpectedEndOfStreamException class with a specified + /// error message and a reference to the inner exception that is the cause of this exception. + /// + /// A message describing the exception. + /// The inner exception + public UnexpectedEndOfStreamException(string message, Exception innerException) : base(message, innerException) { } - /// - /// Initializes a new instance of the UnexpectedEndOfStreamException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected UnexpectedEndOfStreamException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the UnexpectedEndOfStreamException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected UnexpectedEndOfStreamException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs index 398dce811..fcd7c9acd 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs @@ -1,66 +1,65 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib +namespace MelonLoader.ICSharpCode.SharpZipLib; + +/// +/// Indicates that a value was outside of the expected range when decoding an input stream +/// +[Serializable] +public class ValueOutOfRangeException : StreamDecodingException { - /// - /// Indicates that a value was outside of the expected range when decoding an input stream - /// - [Serializable] - public class ValueOutOfRangeException : StreamDecodingException - { - /// - /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable - /// - /// Name of the variable, use: nameof() - public ValueOutOfRangeException(string nameOfValue) - : base($"{nameOfValue} out of range") { } + /// + /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable + /// + /// Name of the variable, use: nameof() + public ValueOutOfRangeException(string nameOfValue) + : base($"{nameOfValue} out of range") { } - /// - /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable, - /// it's current value and expected range. - /// - /// Name of the variable, use: nameof() - /// The invalid value - /// Expected maximum value - /// Expected minimum value - public ValueOutOfRangeException(string nameOfValue, long value, long maxValue, long minValue = 0) - : this(nameOfValue, value.ToString(), maxValue.ToString(), minValue.ToString()) { } + /// + /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable, + /// it's current value and expected range. + /// + /// Name of the variable, use: nameof() + /// The invalid value + /// Expected maximum value + /// Expected minimum value + public ValueOutOfRangeException(string nameOfValue, long value, long maxValue, long minValue = 0) + : this(nameOfValue, value.ToString(), maxValue.ToString(), minValue.ToString()) { } - /// - /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable, - /// it's current value and expected range. - /// - /// Name of the variable, use: nameof() - /// The invalid value - /// Expected maximum value - /// Expected minimum value - public ValueOutOfRangeException(string nameOfValue, string value, string maxValue, string minValue = "0") : - base($"{nameOfValue} out of range: {value}, should be {minValue}..{maxValue}") - { } + /// + /// Initializes a new instance of the ValueOutOfRangeException class naming the causing variable, + /// it's current value and expected range. + /// + /// Name of the variable, use: nameof() + /// The invalid value + /// Expected maximum value + /// Expected minimum value + public ValueOutOfRangeException(string nameOfValue, string value, string maxValue, string minValue = "0") : + base($"{nameOfValue} out of range: {value}, should be {minValue}..{maxValue}") + { } - private ValueOutOfRangeException() - { - } + private ValueOutOfRangeException() + { + } - private ValueOutOfRangeException(string message, Exception innerException) : base(message, innerException) - { - } + private ValueOutOfRangeException(string message, Exception innerException) : base(message, innerException) + { + } - /// - /// Initializes a new instance of the ValueOutOfRangeException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected ValueOutOfRangeException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the ValueOutOfRangeException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected ValueOutOfRangeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs index 1eec50223..017505970 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs @@ -1,545 +1,536 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +#region EventArgs + +/// +/// Event arguments for scanning. +/// +public class ScanEventArgs : EventArgs +{ + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The file or directory name. + public ScanEventArgs(string name) + { + name_ = name; + } + + #endregion Constructors + + /// + /// The file or directory name for this event. + /// + public string Name + { + get { return name_; } + } + + /// + /// Get set a value indicating if scanning should continue or not. + /// + public bool ContinueRunning + { + get { return continueRunning_; } + set { continueRunning_ = value; } + } + + #region Instance Fields + + private readonly string name_; + private bool continueRunning_ = true; + + #endregion Instance Fields +} + +/// +/// Event arguments during processing of a single file or directory. +/// +public class ProgressEventArgs : EventArgs +{ + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The file or directory name if known. + /// The number of bytes processed so far + /// The total number of bytes to process, 0 if not known + public ProgressEventArgs(string name, long processed, long target) + { + name_ = name; + processed_ = processed; + target_ = target; + } + + #endregion Constructors + + /// + /// The name for this event if known. + /// + public string Name + { + get { return name_; } + } + + /// + /// Get set a value indicating whether scanning should continue or not. + /// + public bool ContinueRunning + { + get { return continueRunning_; } + set { continueRunning_ = value; } + } + + /// + /// Get a percentage representing how much of the has been processed + /// + /// 0.0 to 100.0 percent; 0 if target is not known. + public float PercentComplete + { + get + { + var result = target_ <= 0 ? 0 : processed_ / (float)target_ * 100.0f; + return result; + } + } + + /// + /// The number of bytes processed so far + /// + public long Processed + { + get { return processed_; } + } + + /// + /// The number of bytes to process. + /// + /// Target may be 0 or negative if the value isnt known. + public long Target + { + get { return target_; } + } + + #region Instance Fields + + private readonly string name_; + private readonly long processed_; + private readonly long target_; + private bool continueRunning_ = true; + + #endregion Instance Fields +} + +/// +/// Event arguments for directories. +/// +public class DirectoryEventArgs : ScanEventArgs +{ + #region Constructors + + /// + /// Initialize an instance of . + /// + /// The name for this directory. + /// Flag value indicating if any matching files are contained in this directory. + public DirectoryEventArgs(string name, bool hasMatchingFiles) + : base(name) + { + hasMatchingFiles_ = hasMatchingFiles; + } + + #endregion Constructors + + /// + /// Get a value indicating if the directory contains any matching files or not. + /// + public bool HasMatchingFiles + { + get { return hasMatchingFiles_; } + } + + private readonly + + #region Instance Fields + + bool hasMatchingFiles_; + + #endregion Instance Fields +} + +/// +/// Arguments passed when scan failures are detected. +/// +public class ScanFailureEventArgs : EventArgs +{ + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The name to apply. + /// The exception to use. + public ScanFailureEventArgs(string name, Exception e) + { + name_ = name; + exception_ = e; + continueRunning_ = true; + } + + #endregion Constructors + + /// + /// The applicable name. + /// + public string Name + { + get { return name_; } + } + + /// + /// The applicable exception. + /// + public Exception Exception + { + get { return exception_; } + } + + /// + /// Get / set a value indicating whether scanning should continue. + /// + public bool ContinueRunning + { + get { return continueRunning_; } + set { continueRunning_ = value; } + } + + #region Instance Fields + + private readonly string name_; + private readonly Exception exception_; + private bool continueRunning_; + + #endregion Instance Fields +} + +#endregion EventArgs + +#region Delegates + +/// +/// Delegate invoked before starting to process a file. +/// +/// The source of the event +/// The event arguments. +public delegate void ProcessFileHandler(object sender, ScanEventArgs e); + +/// +/// Delegate invoked during processing of a file or directory +/// +/// The source of the event +/// The event arguments. +public delegate void ProgressHandler(object sender, ProgressEventArgs e); + +/// +/// Delegate invoked when a file has been completely processed. +/// +/// The source of the event +/// The event arguments. +public delegate void CompletedFileHandler(object sender, ScanEventArgs e); + +/// +/// Delegate invoked when a directory failure is detected. +/// +/// The source of the event +/// The event arguments. +public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e); + +/// +/// Delegate invoked when a file failure is detected. +/// +/// The source of the event +/// The event arguments. +public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e); + +#endregion Delegates + +/// +/// FileSystemScanner provides facilities scanning of files and directories. +/// +public class FileSystemScanner { - #region EventArgs - - /// - /// Event arguments for scanning. - /// - public class ScanEventArgs : EventArgs - { - #region Constructors - - /// - /// Initialise a new instance of - /// - /// The file or directory name. - public ScanEventArgs(string name) - { - name_ = name; - } - - #endregion Constructors - - /// - /// The file or directory name for this event. - /// - public string Name - { - get { return name_; } - } - - /// - /// Get set a value indicating if scanning should continue or not. - /// - public bool ContinueRunning - { - get { return continueRunning_; } - set { continueRunning_ = value; } - } - - #region Instance Fields - - private string name_; - private bool continueRunning_ = true; - - #endregion Instance Fields - } - - /// - /// Event arguments during processing of a single file or directory. - /// - public class ProgressEventArgs : EventArgs - { - #region Constructors - - /// - /// Initialise a new instance of - /// - /// The file or directory name if known. - /// The number of bytes processed so far - /// The total number of bytes to process, 0 if not known - public ProgressEventArgs(string name, long processed, long target) - { - name_ = name; - processed_ = processed; - target_ = target; - } - - #endregion Constructors - - /// - /// The name for this event if known. - /// - public string Name - { - get { return name_; } - } - - /// - /// Get set a value indicating whether scanning should continue or not. - /// - public bool ContinueRunning - { - get { return continueRunning_; } - set { continueRunning_ = value; } - } - - /// - /// Get a percentage representing how much of the has been processed - /// - /// 0.0 to 100.0 percent; 0 if target is not known. - public float PercentComplete - { - get - { - float result; - if (target_ <= 0) - { - result = 0; - } - else - { - result = ((float)processed_ / (float)target_) * 100.0f; - } - return result; - } - } - - /// - /// The number of bytes processed so far - /// - public long Processed - { - get { return processed_; } - } - - /// - /// The number of bytes to process. - /// - /// Target may be 0 or negative if the value isnt known. - public long Target - { - get { return target_; } - } - - #region Instance Fields - - private string name_; - private long processed_; - private long target_; - private bool continueRunning_ = true; - - #endregion Instance Fields - } - - /// - /// Event arguments for directories. - /// - public class DirectoryEventArgs : ScanEventArgs - { - #region Constructors - - /// - /// Initialize an instance of . - /// - /// The name for this directory. - /// Flag value indicating if any matching files are contained in this directory. - public DirectoryEventArgs(string name, bool hasMatchingFiles) - : base(name) - { - hasMatchingFiles_ = hasMatchingFiles; - } - - #endregion Constructors - - /// - /// Get a value indicating if the directory contains any matching files or not. - /// - public bool HasMatchingFiles - { - get { return hasMatchingFiles_; } - } - - private readonly - - #region Instance Fields - - bool hasMatchingFiles_; - - #endregion Instance Fields - } - - /// - /// Arguments passed when scan failures are detected. - /// - public class ScanFailureEventArgs : EventArgs - { - #region Constructors - - /// - /// Initialise a new instance of - /// - /// The name to apply. - /// The exception to use. - public ScanFailureEventArgs(string name, Exception e) - { - name_ = name; - exception_ = e; - continueRunning_ = true; - } - - #endregion Constructors - - /// - /// The applicable name. - /// - public string Name - { - get { return name_; } - } - - /// - /// The applicable exception. - /// - public Exception Exception - { - get { return exception_; } - } - - /// - /// Get / set a value indicating whether scanning should continue. - /// - public bool ContinueRunning - { - get { return continueRunning_; } - set { continueRunning_ = value; } - } - - #region Instance Fields - - private string name_; - private Exception exception_; - private bool continueRunning_; - - #endregion Instance Fields - } - - #endregion EventArgs - - #region Delegates - - /// - /// Delegate invoked before starting to process a file. - /// - /// The source of the event - /// The event arguments. - public delegate void ProcessFileHandler(object sender, ScanEventArgs e); - - /// - /// Delegate invoked during processing of a file or directory - /// - /// The source of the event - /// The event arguments. - public delegate void ProgressHandler(object sender, ProgressEventArgs e); - - /// - /// Delegate invoked when a file has been completely processed. - /// - /// The source of the event - /// The event arguments. - public delegate void CompletedFileHandler(object sender, ScanEventArgs e); - - /// - /// Delegate invoked when a directory failure is detected. - /// - /// The source of the event - /// The event arguments. - public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e); - - /// - /// Delegate invoked when a file failure is detected. - /// - /// The source of the event - /// The event arguments. - public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e); - - #endregion Delegates - - /// - /// FileSystemScanner provides facilities scanning of files and directories. - /// - public class FileSystemScanner - { - #region Constructors - - /// - /// Initialise a new instance of - /// - /// The file filter to apply when scanning. - public FileSystemScanner(string filter) - { - fileFilter_ = new PathFilter(filter); - } - - /// - /// Initialise a new instance of - /// - /// The file filter to apply. - /// The directory filter to apply. - public FileSystemScanner(string fileFilter, string directoryFilter) - { - fileFilter_ = new PathFilter(fileFilter); - directoryFilter_ = new PathFilter(directoryFilter); - } - - /// - /// Initialise a new instance of - /// - /// The file filter to apply. - public FileSystemScanner(IScanFilter fileFilter) - { - fileFilter_ = fileFilter; - } - - /// - /// Initialise a new instance of - /// - /// The file filter to apply. - /// The directory filter to apply. - public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter) - { - fileFilter_ = fileFilter; - directoryFilter_ = directoryFilter; - } - - #endregion Constructors - - #region Delegates - - /// - /// Delegate to invoke when a directory is processed. - /// - public event EventHandler ProcessDirectory; - - /// - /// Delegate to invoke when a file is processed. - /// - public ProcessFileHandler ProcessFile; - - /// - /// Delegate to invoke when processing for a file has finished. - /// - public CompletedFileHandler CompletedFile; - - /// - /// Delegate to invoke when a directory failure is detected. - /// - public DirectoryFailureHandler DirectoryFailure; - - /// - /// Delegate to invoke when a file failure is detected. - /// - public FileFailureHandler FileFailure; - - #endregion Delegates - - /// - /// Raise the DirectoryFailure event. - /// - /// The directory name. - /// The exception detected. - private bool OnDirectoryFailure(string directory, Exception e) - { - DirectoryFailureHandler handler = DirectoryFailure; - bool result = (handler != null); - if (result) - { - var args = new ScanFailureEventArgs(directory, e); - handler(this, args); - alive_ = args.ContinueRunning; - } - return result; - } - - /// - /// Raise the FileFailure event. - /// - /// The file name. - /// The exception detected. - private bool OnFileFailure(string file, Exception e) - { - FileFailureHandler handler = FileFailure; - - bool result = (handler != null); - - if (result) - { - var args = new ScanFailureEventArgs(file, e); - FileFailure(this, args); - alive_ = args.ContinueRunning; - } - return result; - } - - /// - /// Raise the ProcessFile event. - /// - /// The file name. - private void OnProcessFile(string file) - { - ProcessFileHandler handler = ProcessFile; - - if (handler != null) - { - var args = new ScanEventArgs(file); - handler(this, args); - alive_ = args.ContinueRunning; - } - } - - /// - /// Raise the complete file event - /// - /// The file name - private void OnCompleteFile(string file) - { - CompletedFileHandler handler = CompletedFile; - - if (handler != null) - { - var args = new ScanEventArgs(file); - handler(this, args); - alive_ = args.ContinueRunning; - } - } - - /// - /// Raise the ProcessDirectory event. - /// - /// The directory name. - /// Flag indicating if the directory has matching files. - private void OnProcessDirectory(string directory, bool hasMatchingFiles) - { - EventHandler handler = ProcessDirectory; - - if (handler != null) - { - var args = new DirectoryEventArgs(directory, hasMatchingFiles); - handler(this, args); - alive_ = args.ContinueRunning; - } - } - - /// - /// Scan a directory. - /// - /// The base directory to scan. - /// True to recurse subdirectories, false to scan a single directory. - public void Scan(string directory, bool recurse) - { - alive_ = true; - ScanDir(directory, recurse); - } - - private void ScanDir(string directory, bool recurse) - { - try - { - string[] names = System.IO.Directory.GetFiles(directory); - bool hasMatch = false; - for (int fileIndex = 0; fileIndex < names.Length; ++fileIndex) - { - if (!fileFilter_.IsMatch(names[fileIndex])) - { - names[fileIndex] = null; - } - else - { - hasMatch = true; - } - } - - OnProcessDirectory(directory, hasMatch); - - if (alive_ && hasMatch) - { - foreach (string fileName in names) - { - try - { - if (fileName != null) - { - OnProcessFile(fileName); - if (!alive_) - { - break; - } - } - } - catch (Exception e) - { - if (!OnFileFailure(fileName, e)) - { - throw; - } - } - } - } - } - catch (Exception e) - { - if (!OnDirectoryFailure(directory, e)) - { - throw; - } - } - - if (alive_ && recurse) - { - try - { - string[] names = System.IO.Directory.GetDirectories(directory); - foreach (string fulldir in names) - { - if ((directoryFilter_ == null) || (directoryFilter_.IsMatch(fulldir))) - { - ScanDir(fulldir, true); - if (!alive_) - { - break; - } - } - } - } - catch (Exception e) - { - if (!OnDirectoryFailure(directory, e)) - { - throw; - } - } - } - } - - #region Instance Fields - - /// - /// The file filter currently in use. - /// - private IScanFilter fileFilter_; - - /// - /// The directory filter currently in use. - /// - private IScanFilter directoryFilter_; - - /// - /// Flag indicating if scanning should continue running. - /// - private bool alive_; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The file filter to apply when scanning. + public FileSystemScanner(string filter) + { + fileFilter_ = new PathFilter(filter); + } + + /// + /// Initialise a new instance of + /// + /// The file filter to apply. + /// The directory filter to apply. + public FileSystemScanner(string fileFilter, string directoryFilter) + { + fileFilter_ = new PathFilter(fileFilter); + directoryFilter_ = new PathFilter(directoryFilter); + } + + /// + /// Initialise a new instance of + /// + /// The file filter to apply. + public FileSystemScanner(IScanFilter fileFilter) + { + fileFilter_ = fileFilter; + } + + /// + /// Initialise a new instance of + /// + /// The file filter to apply. + /// The directory filter to apply. + public FileSystemScanner(IScanFilter fileFilter, IScanFilter directoryFilter) + { + fileFilter_ = fileFilter; + directoryFilter_ = directoryFilter; + } + + #endregion Constructors + + #region Delegates + + /// + /// Delegate to invoke when a directory is processed. + /// + public event EventHandler ProcessDirectory; + + /// + /// Delegate to invoke when a file is processed. + /// + public ProcessFileHandler ProcessFile; + + /// + /// Delegate to invoke when processing for a file has finished. + /// + public CompletedFileHandler CompletedFile; + + /// + /// Delegate to invoke when a directory failure is detected. + /// + public DirectoryFailureHandler DirectoryFailure; + + /// + /// Delegate to invoke when a file failure is detected. + /// + public FileFailureHandler FileFailure; + + #endregion Delegates + + /// + /// Raise the DirectoryFailure event. + /// + /// The directory name. + /// The exception detected. + private bool OnDirectoryFailure(string directory, Exception e) + { + var handler = DirectoryFailure; + var result = handler != null; + if (result) + { + var args = new ScanFailureEventArgs(directory, e); + handler(this, args); + alive_ = args.ContinueRunning; + } + return result; + } + + /// + /// Raise the FileFailure event. + /// + /// The file name. + /// The exception detected. + private bool OnFileFailure(string file, Exception e) + { + var handler = FileFailure; + + var result = handler != null; + + if (result) + { + var args = new ScanFailureEventArgs(file, e); + FileFailure(this, args); + alive_ = args.ContinueRunning; + } + return result; + } + + /// + /// Raise the ProcessFile event. + /// + /// The file name. + private void OnProcessFile(string file) + { + var handler = ProcessFile; + + if (handler != null) + { + var args = new ScanEventArgs(file); + handler(this, args); + alive_ = args.ContinueRunning; + } + } + + /// + /// Raise the complete file event + /// + /// The file name + private void OnCompleteFile(string file) + { + var handler = CompletedFile; + + if (handler != null) + { + var args = new ScanEventArgs(file); + handler(this, args); + alive_ = args.ContinueRunning; + } + } + + /// + /// Raise the ProcessDirectory event. + /// + /// The directory name. + /// Flag indicating if the directory has matching files. + private void OnProcessDirectory(string directory, bool hasMatchingFiles) + { + var handler = ProcessDirectory; + + if (handler != null) + { + var args = new DirectoryEventArgs(directory, hasMatchingFiles); + handler(this, args); + alive_ = args.ContinueRunning; + } + } + + /// + /// Scan a directory. + /// + /// The base directory to scan. + /// True to recurse subdirectories, false to scan a single directory. + public void Scan(string directory, bool recurse) + { + alive_ = true; + ScanDir(directory, recurse); + } + + private void ScanDir(string directory, bool recurse) + { + try + { + var names = System.IO.Directory.GetFiles(directory); + var hasMatch = false; + for (var fileIndex = 0; fileIndex < names.Length; ++fileIndex) + { + if (!fileFilter_.IsMatch(names[fileIndex])) + { + names[fileIndex] = null; + } + else + { + hasMatch = true; + } + } + + OnProcessDirectory(directory, hasMatch); + + if (alive_ && hasMatch) + { + foreach (var fileName in names) + { + try + { + if (fileName != null) + { + OnProcessFile(fileName); + if (!alive_) + { + break; + } + } + } + catch (Exception e) + { + if (!OnFileFailure(fileName, e)) + { + throw; + } + } + } + } + } + catch (Exception e) + { + if (!OnDirectoryFailure(directory, e)) + { + throw; + } + } + + if (alive_ && recurse) + { + try + { + var names = System.IO.Directory.GetDirectories(directory); + foreach (var fulldir in names) + { + if ((directoryFilter_ == null) || directoryFilter_.IsMatch(fulldir)) + { + ScanDir(fulldir, true); + if (!alive_) + { + break; + } + } + } + } + catch (Exception e) + { + if (!OnDirectoryFailure(directory, e)) + { + throw; + } + } + } + } + + #region Instance Fields + + /// + /// The file filter currently in use. + /// + private readonly IScanFilter fileFilter_; + + /// + /// The directory filter currently in use. + /// + private readonly IScanFilter directoryFilter_; + + /// + /// Flag indicating if scanning should continue running. + /// + private bool alive_; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs index ead235629..8db45bb03 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs @@ -1,22 +1,21 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +/// +/// INameTransform defines how file system names are transformed for use with archives, or vice versa. +/// +public interface INameTransform { - /// - /// INameTransform defines how file system names are transformed for use with archives, or vice versa. - /// - public interface INameTransform - { - /// - /// Given a file name determine the transformed value. - /// - /// The name to transform. - /// The transformed file name. - string TransformFile(string name); + /// + /// Given a file name determine the transformed value. + /// + /// The name to transform. + /// The transformed file name. + string TransformFile(string name); - /// - /// Given a directory name determine the transformed value. - /// - /// The name to transform. - /// The transformed directory name - string TransformDirectory(string name); - } + /// + /// Given a directory name determine the transformed value. + /// + /// The name to transform. + /// The transformed directory name + string TransformDirectory(string name); } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs index 7aca1126c..332ef67e0 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs @@ -1,15 +1,14 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +/// +/// Scanning filters support filtering of names. +/// +public interface IScanFilter { - /// - /// Scanning filters support filtering of names. - /// - public interface IScanFilter - { - /// - /// Test a name to see if it 'matches' the filter. - /// - /// The name to test. - /// Returns true if the name matches the filter, false if it does not match. - bool IsMatch(string name); - } + /// + /// Test a name to see if it 'matches' the filter. + /// + /// The name to test. + /// Returns true if the name matches the filter, false if it does not match. + bool IsMatch(string name); } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs index 70b496cce..6adcfe577 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs @@ -1,53 +1,52 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +/// +/// InvalidNameException is thrown for invalid names such as directory traversal paths and names with invalid characters +/// +[Serializable] +public class InvalidNameException : SharpZipBaseException { - /// - /// InvalidNameException is thrown for invalid names such as directory traversal paths and names with invalid characters - /// - [Serializable] - public class InvalidNameException : SharpZipBaseException - { - /// - /// Initializes a new instance of the InvalidNameException class with a default error message. - /// - public InvalidNameException() : base("An invalid name was specified") - { - } + /// + /// Initializes a new instance of the InvalidNameException class with a default error message. + /// + public InvalidNameException() : base("An invalid name was specified") + { + } - /// - /// Initializes a new instance of the InvalidNameException class with a specified error message. - /// - /// A message describing the exception. - public InvalidNameException(string message) : base(message) - { - } + /// + /// Initializes a new instance of the InvalidNameException class with a specified error message. + /// + /// A message describing the exception. + public InvalidNameException(string message) : base(message) + { + } - /// - /// Initializes a new instance of the InvalidNameException class with a specified - /// error message and a reference to the inner exception that is the cause of this exception. - /// - /// A message describing the exception. - /// The inner exception - public InvalidNameException(string message, Exception innerException) : base(message, innerException) - { - } + /// + /// Initializes a new instance of the InvalidNameException class with a specified + /// error message and a reference to the inner exception that is the cause of this exception. + /// + /// A message describing the exception. + /// The inner exception + public InvalidNameException(string message, Exception innerException) : base(message, innerException) + { + } - /// - /// Initializes a new instance of the InvalidNameException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected InvalidNameException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the InvalidNameException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected InvalidNameException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs index d77c179a3..939f76769 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs @@ -3,282 +3,273 @@ using System.Text; using System.Text.RegularExpressions; -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +/// +/// NameFilter is a string matching class which allows for both positive and negative +/// matching. +/// A filter is a sequence of independant regular expressions separated by semi-colons ';'. +/// To include a semi-colon it may be quoted as in \;. Each expression can be prefixed by a plus '+' sign or +/// a minus '-' sign to denote the expression is intended to include or exclude names. +/// If neither a plus or minus sign is found include is the default. +/// A given name is tested for inclusion before checking exclusions. Only names matching an include spec +/// and not matching an exclude spec are deemed to match the filter. +/// An empty filter matches any name. +/// +/// The following expression includes all name ending in '.dat' with the exception of 'dummy.dat' +/// "+\.dat$;-^dummy\.dat$" +/// +public class NameFilter : IScanFilter { - /// - /// NameFilter is a string matching class which allows for both positive and negative - /// matching. - /// A filter is a sequence of independant regular expressions separated by semi-colons ';'. - /// To include a semi-colon it may be quoted as in \;. Each expression can be prefixed by a plus '+' sign or - /// a minus '-' sign to denote the expression is intended to include or exclude names. - /// If neither a plus or minus sign is found include is the default. - /// A given name is tested for inclusion before checking exclusions. Only names matching an include spec - /// and not matching an exclude spec are deemed to match the filter. - /// An empty filter matches any name. - /// - /// The following expression includes all name ending in '.dat' with the exception of 'dummy.dat' - /// "+\.dat$;-^dummy\.dat$" - /// - public class NameFilter : IScanFilter - { - #region Constructors + #region Constructors - /// - /// Construct an instance based on the filter expression passed - /// - /// The filter expression. - public NameFilter(string filter) - { - filter_ = filter; - inclusions_ = new List(); - exclusions_ = new List(); - Compile(); - } + /// + /// Construct an instance based on the filter expression passed + /// + /// The filter expression. + public NameFilter(string filter) + { + filter_ = filter; + inclusions_ = []; + exclusions_ = []; + Compile(); + } - #endregion Constructors + #endregion Constructors - /// - /// Test a string to see if it is a valid regular expression. - /// - /// The expression to test. - /// True if expression is a valid false otherwise. - public static bool IsValidExpression(string expression) - { - bool result = true; - try - { - var exp = new Regex(expression, RegexOptions.IgnoreCase | RegexOptions.Singleline); - } - catch (ArgumentException) - { - result = false; - } - return result; - } + /// + /// Test a string to see if it is a valid regular expression. + /// + /// The expression to test. + /// True if expression is a valid false otherwise. + public static bool IsValidExpression(string expression) + { + var result = true; + try + { + var exp = new Regex(expression, RegexOptions.IgnoreCase | RegexOptions.Singleline); + } + catch (ArgumentException) + { + result = false; + } + return result; + } - /// - /// Test an expression to see if it is valid as a filter. - /// - /// The filter expression to test. - /// True if the expression is valid, false otherwise. - public static bool IsValidFilterExpression(string toTest) - { - bool result = true; + /// + /// Test an expression to see if it is valid as a filter. + /// + /// The filter expression to test. + /// True if the expression is valid, false otherwise. + public static bool IsValidFilterExpression(string toTest) + { + var result = true; - try - { - if (toTest != null) - { - string[] items = SplitQuoted(toTest); - for (int i = 0; i < items.Length; ++i) - { - if ((items[i] != null) && (items[i].Length > 0)) - { - string toCompile; + try + { + if (toTest != null) + { + var items = SplitQuoted(toTest); + for (var i = 0; i < items.Length; ++i) + { + if ((items[i] != null) && (items[i].Length > 0)) + { + string toCompile; - if (items[i][0] == '+') - { - toCompile = items[i].Substring(1, items[i].Length - 1); - } - else if (items[i][0] == '-') - { - toCompile = items[i].Substring(1, items[i].Length - 1); - } - else - { - toCompile = items[i]; - } + if (items[i][0] == '+') + { + toCompile = items[i][1..]; + } + else + { + toCompile = items[i][0] == '-' ? items[i][1..] : items[i]; + } - var testRegex = new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Singleline); - } - } - } - } - catch (ArgumentException) - { - result = false; - } + var testRegex = new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Singleline); + } + } + } + } + catch (ArgumentException) + { + result = false; + } - return result; - } + return result; + } - /// - /// Split a string into its component pieces - /// - /// The original string - /// Returns an array of values containing the individual filter elements. - public static string[] SplitQuoted(string original) - { - char escape = '\\'; - char[] separators = { ';' }; + /// + /// Split a string into its component pieces + /// + /// The original string + /// Returns an array of values containing the individual filter elements. + public static string[] SplitQuoted(string original) + { + var escape = '\\'; + char[] separators = { ';' }; - var result = new List(); + var result = new List(); - if (!string.IsNullOrEmpty(original)) - { - int endIndex = -1; - var b = new StringBuilder(); + if (!string.IsNullOrEmpty(original)) + { + var endIndex = -1; + var b = new StringBuilder(); - while (endIndex < original.Length) - { - endIndex += 1; - if (endIndex >= original.Length) - { - result.Add(b.ToString()); - } - else if (original[endIndex] == escape) - { - endIndex += 1; - if (endIndex >= original.Length) - { - throw new ArgumentException("Missing terminating escape character", nameof(original)); - } - // include escape if this is not an escaped separator - if (Array.IndexOf(separators, original[endIndex]) < 0) - b.Append(escape); + while (endIndex < original.Length) + { + endIndex += 1; + if (endIndex >= original.Length) + { + result.Add(b.ToString()); + } + else if (original[endIndex] == escape) + { + endIndex += 1; + if (endIndex >= original.Length) + { + throw new ArgumentException("Missing terminating escape character", nameof(original)); + } + // include escape if this is not an escaped separator + if (Array.IndexOf(separators, original[endIndex]) < 0) + b.Append(escape); - b.Append(original[endIndex]); - } - else - { - if (Array.IndexOf(separators, original[endIndex]) >= 0) - { - result.Add(b.ToString()); - b.Length = 0; - } - else - { - b.Append(original[endIndex]); - } - } - } - } + b.Append(original[endIndex]); + } + else + { + if (Array.IndexOf(separators, original[endIndex]) >= 0) + { + result.Add(b.ToString()); + b.Length = 0; + } + else + { + b.Append(original[endIndex]); + } + } + } + } - return result.ToArray(); - } + return result.ToArray(); + } - /// - /// Convert this filter to its string equivalent. - /// - /// The string equivalent for this filter. - public override string ToString() - { - return filter_; - } + /// + /// Convert this filter to its string equivalent. + /// + /// The string equivalent for this filter. + public override string ToString() + { + return filter_; + } - /// - /// Test a value to see if it is included by the filter. - /// - /// The value to test. - /// True if the value is included, false otherwise. - public bool IsIncluded(string name) - { - bool result = false; - if (inclusions_.Count == 0) - { - result = true; - } - else - { - foreach (Regex r in inclusions_) - { - if (r.IsMatch(name)) - { - result = true; - break; - } - } - } - return result; - } + /// + /// Test a value to see if it is included by the filter. + /// + /// The value to test. + /// True if the value is included, false otherwise. + public bool IsIncluded(string name) + { + var result = false; + if (inclusions_.Count == 0) + { + result = true; + } + else + { + foreach (var r in inclusions_) + { + if (r.IsMatch(name)) + { + result = true; + break; + } + } + } + return result; + } - /// - /// Test a value to see if it is excluded by the filter. - /// - /// The value to test. - /// True if the value is excluded, false otherwise. - public bool IsExcluded(string name) - { - bool result = false; - foreach (Regex r in exclusions_) - { - if (r.IsMatch(name)) - { - result = true; - break; - } - } - return result; - } + /// + /// Test a value to see if it is excluded by the filter. + /// + /// The value to test. + /// True if the value is excluded, false otherwise. + public bool IsExcluded(string name) + { + var result = false; + foreach (var r in exclusions_) + { + if (r.IsMatch(name)) + { + result = true; + break; + } + } + return result; + } - #region IScanFilter Members + #region IScanFilter Members - /// - /// Test a value to see if it matches the filter. - /// - /// The value to test. - /// True if the value matches, false otherwise. - public bool IsMatch(string name) - { - return (IsIncluded(name) && !IsExcluded(name)); - } + /// + /// Test a value to see if it matches the filter. + /// + /// The value to test. + /// True if the value matches, false otherwise. + public bool IsMatch(string name) + { + return IsIncluded(name) && !IsExcluded(name); + } - #endregion IScanFilter Members + #endregion IScanFilter Members - /// - /// Compile this filter. - /// - private void Compile() - { - // TODO: Check to see if combining RE's makes it faster/smaller. - // simple scheme would be to have one RE for inclusion and one for exclusion. - if (filter_ == null) - { - return; - } + /// + /// Compile this filter. + /// + private void Compile() + { + // TODO: Check to see if combining RE's makes it faster/smaller. + // simple scheme would be to have one RE for inclusion and one for exclusion. + if (filter_ == null) + { + return; + } - string[] items = SplitQuoted(filter_); - for (int i = 0; i < items.Length; ++i) - { - if ((items[i] != null) && (items[i].Length > 0)) - { - bool include = (items[i][0] != '-'); - string toCompile; + var items = SplitQuoted(filter_); + for (var i = 0; i < items.Length; ++i) + { + if ((items[i] != null) && (items[i].Length > 0)) + { + var include = items[i][0] != '-'; + string toCompile; - if (items[i][0] == '+') - { - toCompile = items[i].Substring(1, items[i].Length - 1); - } - else if (items[i][0] == '-') - { - toCompile = items[i].Substring(1, items[i].Length - 1); - } - else - { - toCompile = items[i]; - } + if (items[i][0] == '+') + { + toCompile = items[i][1..]; + } + else + { + toCompile = items[i][0] == '-' ? items[i][1..] : items[i]; + } - // NOTE: Regular expressions can fail to compile here for a number of reasons that cause an exception - // these are left unhandled here as the caller is responsible for ensuring all is valid. - // several functions IsValidFilterExpression and IsValidExpression are provided for such checking - if (include) - { - inclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)); - } - else - { - exclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)); - } - } - } - } + // NOTE: Regular expressions can fail to compile here for a number of reasons that cause an exception + // these are left unhandled here as the caller is responsible for ensuring all is valid. + // several functions IsValidFilterExpression and IsValidExpression are provided for such checking + if (include) + { + inclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)); + } + else + { + exclusions_.Add(new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)); + } + } + } + } - #region Instance Fields + #region Instance Fields - private string filter_; - private List inclusions_; - private List exclusions_; + private readonly string filter_; + private readonly List inclusions_; + private readonly List exclusions_; - #endregion Instance Fields - } + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs index c4b94cda1..98d68c461 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs @@ -1,318 +1,317 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +/// +/// PathFilter filters directories and files using a form of regular expressions +/// by full path name. +/// See NameFilter for more detail on filtering. +/// +public class PathFilter : IScanFilter { - /// - /// PathFilter filters directories and files using a form of regular expressions - /// by full path name. - /// See NameFilter for more detail on filtering. - /// - public class PathFilter : IScanFilter - { - #region Constructors - - /// - /// Initialise a new instance of . - /// - /// The filter expression to apply. - public PathFilter(string filter) - { - nameFilter_ = new NameFilter(filter); - } - - #endregion Constructors - - #region IScanFilter Members - - /// - /// Test a name to see if it matches the filter. - /// - /// The name to test. - /// True if the name matches, false otherwise. - /// is used to get the full path before matching. - public virtual bool IsMatch(string name) - { - bool result = false; - - if (name != null) - { - string cooked = (name.Length > 0) ? Path.GetFullPath(name) : ""; - result = nameFilter_.IsMatch(cooked); - } - return result; - } - - private readonly - - #endregion IScanFilter Members - - #region Instance Fields - - NameFilter nameFilter_; - - #endregion Instance Fields - } - - /// - /// ExtendedPathFilter filters based on name, file size, and the last write time of the file. - /// - /// Provides an example of how to customise filtering. - public class ExtendedPathFilter : PathFilter - { - #region Constructors - - /// - /// Initialise a new instance of ExtendedPathFilter. - /// - /// The filter to apply. - /// The minimum file size to include. - /// The maximum file size to include. - public ExtendedPathFilter(string filter, - long minSize, long maxSize) - : base(filter) - { - MinSize = minSize; - MaxSize = maxSize; - } - - /// - /// Initialise a new instance of ExtendedPathFilter. - /// - /// The filter to apply. - /// The minimum to include. - /// The maximum to include. - public ExtendedPathFilter(string filter, - DateTime minDate, DateTime maxDate) - : base(filter) - { - MinDate = minDate; - MaxDate = maxDate; - } - - /// - /// Initialise a new instance of ExtendedPathFilter. - /// - /// The filter to apply. - /// The minimum file size to include. - /// The maximum file size to include. - /// The minimum to include. - /// The maximum to include. - public ExtendedPathFilter(string filter, - long minSize, long maxSize, - DateTime minDate, DateTime maxDate) - : base(filter) - { - MinSize = minSize; - MaxSize = maxSize; - MinDate = minDate; - MaxDate = maxDate; - } - - #endregion Constructors - - #region IScanFilter Members - - /// - /// Test a filename to see if it matches the filter. - /// - /// The filename to test. - /// True if the filter matches, false otherwise. - /// The doesnt exist - public override bool IsMatch(string name) - { - bool result = base.IsMatch(name); - - if (result) - { - var fileInfo = new FileInfo(name); - result = - (MinSize <= fileInfo.Length) && - (MaxSize >= fileInfo.Length) && - (MinDate <= fileInfo.LastWriteTime) && - (MaxDate >= fileInfo.LastWriteTime) - ; - } - return result; - } - - #endregion IScanFilter Members - - #region Properties - - /// - /// Get/set the minimum size/length for a file that will match this filter. - /// - /// The default value is zero. - /// value is less than zero; greater than - public long MinSize - { - get { return minSize_; } - set - { - if ((value < 0) || (maxSize_ < value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - minSize_ = value; - } - } - - /// - /// Get/set the maximum size/length for a file that will match this filter. - /// - /// The default value is - /// value is less than zero or less than - public long MaxSize - { - get { return maxSize_; } - set - { - if ((value < 0) || (minSize_ > value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - maxSize_ = value; - } - } - - /// - /// Get/set the minimum value that will match for this filter. - /// - /// Files with a LastWrite time less than this value are excluded by the filter. - public DateTime MinDate - { - get - { - return minDate_; - } - - set - { - if (value > maxDate_) - { - throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MaxDate"); - } - - minDate_ = value; - } - } - - /// - /// Get/set the maximum value that will match for this filter. - /// - /// Files with a LastWrite time greater than this value are excluded by the filter. - public DateTime MaxDate - { - get - { - return maxDate_; - } - - set - { - if (minDate_ > value) - { - throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MinDate"); - } - - maxDate_ = value; - } - } - - #endregion Properties - - #region Instance Fields - - private long minSize_; - private long maxSize_ = long.MaxValue; - private DateTime minDate_ = DateTime.MinValue; - private DateTime maxDate_ = DateTime.MaxValue; - - #endregion Instance Fields - } - - /// - /// NameAndSizeFilter filters based on name and file size. - /// - /// A sample showing how filters might be extended. - [Obsolete("Use ExtendedPathFilter instead")] - public class NameAndSizeFilter : PathFilter - { - /// - /// Initialise a new instance of NameAndSizeFilter. - /// - /// The filter to apply. - /// The minimum file size to include. - /// The maximum file size to include. - public NameAndSizeFilter(string filter, long minSize, long maxSize) - : base(filter) - { - MinSize = minSize; - MaxSize = maxSize; - } - - /// - /// Test a filename to see if it matches the filter. - /// - /// The filename to test. - /// True if the filter matches, false otherwise. - public override bool IsMatch(string name) - { - bool result = base.IsMatch(name); - - if (result) - { - var fileInfo = new FileInfo(name); - long length = fileInfo.Length; - result = - (MinSize <= length) && - (MaxSize >= length); - } - return result; - } - - /// - /// Get/set the minimum size for a file that will match this filter. - /// - public long MinSize - { - get { return minSize_; } - set - { - if ((value < 0) || (maxSize_ < value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - minSize_ = value; - } - } - - /// - /// Get/set the maximum size for a file that will match this filter. - /// - public long MaxSize - { - get { return maxSize_; } - set - { - if ((value < 0) || (minSize_ > value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - maxSize_ = value; - } - } - - #region Instance Fields - - private long minSize_; - private long maxSize_ = long.MaxValue; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Initialise a new instance of . + /// + /// The filter expression to apply. + public PathFilter(string filter) + { + nameFilter_ = new NameFilter(filter); + } + + #endregion Constructors + + #region IScanFilter Members + + /// + /// Test a name to see if it matches the filter. + /// + /// The name to test. + /// True if the name matches, false otherwise. + /// is used to get the full path before matching. + public virtual bool IsMatch(string name) + { + var result = false; + + if (name != null) + { + var cooked = (name.Length > 0) ? Path.GetFullPath(name) : ""; + result = nameFilter_.IsMatch(cooked); + } + return result; + } + + private readonly + + #endregion IScanFilter Members + + #region Instance Fields + + NameFilter nameFilter_; + + #endregion Instance Fields +} + +/// +/// ExtendedPathFilter filters based on name, file size, and the last write time of the file. +/// +/// Provides an example of how to customise filtering. +public class ExtendedPathFilter : PathFilter +{ + #region Constructors + + /// + /// Initialise a new instance of ExtendedPathFilter. + /// + /// The filter to apply. + /// The minimum file size to include. + /// The maximum file size to include. + public ExtendedPathFilter(string filter, + long minSize, long maxSize) + : base(filter) + { + MinSize = minSize; + MaxSize = maxSize; + } + + /// + /// Initialise a new instance of ExtendedPathFilter. + /// + /// The filter to apply. + /// The minimum to include. + /// The maximum to include. + public ExtendedPathFilter(string filter, + DateTime minDate, DateTime maxDate) + : base(filter) + { + MinDate = minDate; + MaxDate = maxDate; + } + + /// + /// Initialise a new instance of ExtendedPathFilter. + /// + /// The filter to apply. + /// The minimum file size to include. + /// The maximum file size to include. + /// The minimum to include. + /// The maximum to include. + public ExtendedPathFilter(string filter, + long minSize, long maxSize, + DateTime minDate, DateTime maxDate) + : base(filter) + { + MinSize = minSize; + MaxSize = maxSize; + MinDate = minDate; + MaxDate = maxDate; + } + + #endregion Constructors + + #region IScanFilter Members + + /// + /// Test a filename to see if it matches the filter. + /// + /// The filename to test. + /// True if the filter matches, false otherwise. + /// The doesnt exist + public override bool IsMatch(string name) + { + var result = base.IsMatch(name); + + if (result) + { + var fileInfo = new FileInfo(name); + result = + (MinSize <= fileInfo.Length) && + (MaxSize >= fileInfo.Length) && + (MinDate <= fileInfo.LastWriteTime) && + (MaxDate >= fileInfo.LastWriteTime) + ; + } + return result; + } + + #endregion IScanFilter Members + + #region Properties + + /// + /// Get/set the minimum size/length for a file that will match this filter. + /// + /// The default value is zero. + /// value is less than zero; greater than + public long MinSize + { + get { return minSize_; } + set + { + if ((value < 0) || (maxSize_ < value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + minSize_ = value; + } + } + + /// + /// Get/set the maximum size/length for a file that will match this filter. + /// + /// The default value is + /// value is less than zero or less than + public long MaxSize + { + get { return maxSize_; } + set + { + if ((value < 0) || (minSize_ > value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + maxSize_ = value; + } + } + + /// + /// Get/set the minimum value that will match for this filter. + /// + /// Files with a LastWrite time less than this value are excluded by the filter. + public DateTime MinDate + { + get + { + return minDate_; + } + + set + { + if (value > maxDate_) + { + throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MaxDate"); + } + + minDate_ = value; + } + } + + /// + /// Get/set the maximum value that will match for this filter. + /// + /// Files with a LastWrite time greater than this value are excluded by the filter. + public DateTime MaxDate + { + get + { + return maxDate_; + } + + set + { + if (minDate_ > value) + { + throw new ArgumentOutOfRangeException(nameof(value), "Exceeds MinDate"); + } + + maxDate_ = value; + } + } + + #endregion Properties + + #region Instance Fields + + private long minSize_; + private long maxSize_ = long.MaxValue; + private DateTime minDate_ = DateTime.MinValue; + private DateTime maxDate_ = DateTime.MaxValue; + + #endregion Instance Fields +} + +/// +/// NameAndSizeFilter filters based on name and file size. +/// +/// A sample showing how filters might be extended. +[Obsolete("Use ExtendedPathFilter instead")] +public class NameAndSizeFilter : PathFilter +{ + /// + /// Initialise a new instance of NameAndSizeFilter. + /// + /// The filter to apply. + /// The minimum file size to include. + /// The maximum file size to include. + public NameAndSizeFilter(string filter, long minSize, long maxSize) + : base(filter) + { + MinSize = minSize; + MaxSize = maxSize; + } + + /// + /// Test a filename to see if it matches the filter. + /// + /// The filename to test. + /// True if the filter matches, false otherwise. + public override bool IsMatch(string name) + { + var result = base.IsMatch(name); + + if (result) + { + var fileInfo = new FileInfo(name); + var length = fileInfo.Length; + result = + (MinSize <= length) && + (MaxSize >= length); + } + return result; + } + + /// + /// Get/set the minimum size for a file that will match this filter. + /// + public long MinSize + { + get { return minSize_; } + set + { + if ((value < 0) || (maxSize_ < value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + minSize_ = value; + } + } + + /// + /// Get/set the maximum size for a file that will match this filter. + /// + public long MaxSize + { + get { return maxSize_; } + set + { + if ((value < 0) || (minSize_ > value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + maxSize_ = value; + } + } + + #region Instance Fields + + private long minSize_; + private long maxSize_ = long.MaxValue; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs index afc274fe0..712de7bc5 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs @@ -2,53 +2,53 @@ using System.IO; using System.Linq; -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +/// +/// PathUtils provides simple utilities for handling paths. +/// +public static class PathUtils { - /// - /// PathUtils provides simple utilities for handling paths. - /// - public static class PathUtils - { - /// - /// Remove any path root present in the path - /// - /// A containing path information. - /// The path with the root removed if it was present; path otherwise. - public static string DropPathRoot(string path) - { - var invalidChars = Path.GetInvalidPathChars(); - // If the first character after the root is a ':', .NET < 4.6.2 throws - var cleanRootSep = path.Length >= 3 && path[1] == ':' && path[2] == ':'; - - // Replace any invalid path characters with '_' to prevent Path.GetPathRoot from throwing. - // Only pass the first 258 (should be 260, but that still throws for some reason) characters - // as .NET < 4.6.2 throws on longer paths - var cleanPath = new string(path.Take(258) - .Select( (c, i) => invalidChars.Contains(c) || (i == 2 && cleanRootSep) ? '_' : c).ToArray()); + /// + /// Remove any path root present in the path + /// + /// A containing path information. + /// The path with the root removed if it was present; path otherwise. + public static string DropPathRoot(string path) + { + var invalidChars = Path.GetInvalidPathChars(); + // If the first character after the root is a ':', .NET < 4.6.2 throws + var cleanRootSep = path.Length >= 3 && path[1] == ':' && path[2] == ':'; + + // Replace any invalid path characters with '_' to prevent Path.GetPathRoot from throwing. + // Only pass the first 258 (should be 260, but that still throws for some reason) characters + // as .NET < 4.6.2 throws on longer paths + var cleanPath = new string(path.Take(258) + .Select((c, i) => invalidChars.Contains(c) || (i == 2 && cleanRootSep) ? '_' : c).ToArray()); - var stripLength = Path.GetPathRoot(cleanPath).Length; - while (path.Length > stripLength && (path[stripLength] == '/' || path[stripLength] == '\\')) stripLength++; - return path.Substring(stripLength); - } + var stripLength = Path.GetPathRoot(cleanPath).Length; + while (path.Length > stripLength && (path[stripLength] == '/' || path[stripLength] == '\\')) + stripLength++; + return path[stripLength..]; + } - /// - /// Returns a random file name in the users temporary directory, or in directory of if specified - /// - /// If specified, used as the base file name for the temporary file - /// Returns a temporary file name - public static string GetTempFileName(string original = null) - { - string fileName; - var tempPath = Path.GetTempPath(); + /// + /// Returns a random file name in the users temporary directory, or in directory of if specified + /// + /// If specified, used as the base file name for the temporary file + /// Returns a temporary file name + public static string GetTempFileName(string original = null) + { + string fileName; + var tempPath = Path.GetTempPath(); - do - { - fileName = original == null - ? Path.Combine(tempPath, Path.GetRandomFileName()) - : $"{original}.{Path.GetRandomFileName()}"; - } while (File.Exists(fileName)); + do + { + fileName = original == null + ? Path.Combine(tempPath, Path.GetRandomFileName()) + : $"{original}.{Path.GetRandomFileName()}"; + } while (File.Exists(fileName)); - return fileName; - } - } + return fileName; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs index f552b6be2..70737b4c5 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs @@ -1,284 +1,283 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Core +namespace MelonLoader.ICSharpCode.SharpZipLib.Core; + +/// +/// Provides simple " utilities. +/// +public sealed class StreamUtils { - /// - /// Provides simple " utilities. - /// - public sealed class StreamUtils - { - /// - /// Read from a ensuring all the required data is read. - /// - /// The stream to read. - /// The buffer to fill. - /// - static public void ReadFully(Stream stream, byte[] buffer) - { - ReadFully(stream, buffer, 0, buffer.Length); - } - - /// - /// Read from a " ensuring all the required data is read. - /// - /// The stream to read data from. - /// The buffer to store data in. - /// The offset at which to begin storing data. - /// The number of bytes of data to store. - /// Required parameter is null - /// and or are invalid. - /// End of stream is encountered before all the data has been read. - static public void ReadFully(Stream stream, byte[] buffer, int offset, int count) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - // Offset can equal length when buffer and count are 0. - if ((offset < 0) || (offset > buffer.Length)) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if ((count < 0) || (offset + count > buffer.Length)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - while (count > 0) - { - int readCount = stream.Read(buffer, offset, count); - if (readCount <= 0) - { - throw new EndOfStreamException(); - } - offset += readCount; - count -= readCount; - } - } - - /// - /// Read as much data as possible from a ", up to the requested number of bytes - /// - /// The stream to read data from. - /// The buffer to store data in. - /// The offset at which to begin storing data. - /// The number of bytes of data to store. - /// Required parameter is null - /// and or are invalid. - static public int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - // Offset can equal length when buffer and count are 0. - if ((offset < 0) || (offset > buffer.Length)) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if ((count < 0) || (offset + count > buffer.Length)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - int totalReadCount = 0; - while (count > 0) - { - int readCount = stream.Read(buffer, offset, count); - if (readCount <= 0) - { - break; - } - offset += readCount; - count -= readCount; - totalReadCount += readCount; - } - - return totalReadCount; - } - - /// - /// Copy the contents of one to another. - /// - /// The stream to source data from. - /// The stream to write data to. - /// The buffer to use during copying. - static public void Copy(Stream source, Stream destination, byte[] buffer) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (destination == null) - { - throw new ArgumentNullException(nameof(destination)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - // Ensure a reasonable size of buffer is used without being prohibitive. - if (buffer.Length < 128) - { - throw new ArgumentException("Buffer is too small", nameof(buffer)); - } - - bool copying = true; - - while (copying) - { - int bytesRead = source.Read(buffer, 0, buffer.Length); - if (bytesRead > 0) - { - destination.Write(buffer, 0, bytesRead); - } - else - { - destination.Flush(); - copying = false; - } - } - } - - /// - /// Copy the contents of one to another. - /// - /// The stream to source data from. - /// The stream to write data to. - /// The buffer to use during copying. - /// The progress handler delegate to use. - /// The minimum between progress updates. - /// The source for this event. - /// The name to use with the event. - /// This form is specialised for use within #Zip to support events during archive operations. - static public void Copy(Stream source, Stream destination, - byte[] buffer, ProgressHandler progressHandler, TimeSpan updateInterval, object sender, string name) - { - Copy(source, destination, buffer, progressHandler, updateInterval, sender, name, -1); - } - - /// - /// Copy the contents of one to another. - /// - /// The stream to source data from. - /// The stream to write data to. - /// The buffer to use during copying. - /// The progress handler delegate to use. - /// The minimum between progress updates. - /// The source for this event. - /// The name to use with the event. - /// A predetermined fixed target value to use with progress updates. - /// If the value is negative the target is calculated by looking at the stream. - /// This form is specialised for use within #Zip to support events during archive operations. - static public void Copy(Stream source, Stream destination, - byte[] buffer, - ProgressHandler progressHandler, TimeSpan updateInterval, - object sender, string name, long fixedTarget) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (destination == null) - { - throw new ArgumentNullException(nameof(destination)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - // Ensure a reasonable size of buffer is used without being prohibitive. - if (buffer.Length < 128) - { - throw new ArgumentException("Buffer is too small", nameof(buffer)); - } - - if (progressHandler == null) - { - throw new ArgumentNullException(nameof(progressHandler)); - } - - bool copying = true; - - DateTime marker = DateTime.Now; - long processed = 0; - long target = 0; - - if (fixedTarget >= 0) - { - target = fixedTarget; - } - else if (source.CanSeek) - { - target = source.Length - source.Position; - } - - // Always fire 0% progress.. - var args = new ProgressEventArgs(name, processed, target); - progressHandler(sender, args); - - bool progressFired = true; - - while (copying) - { - int bytesRead = source.Read(buffer, 0, buffer.Length); - if (bytesRead > 0) - { - processed += bytesRead; - progressFired = false; - destination.Write(buffer, 0, bytesRead); - } - else - { - destination.Flush(); - copying = false; - } - - if (DateTime.Now - marker > updateInterval) - { - progressFired = true; - marker = DateTime.Now; - args = new ProgressEventArgs(name, processed, target); - progressHandler(sender, args); - - copying = args.ContinueRunning; - } - } - - if (!progressFired) - { - args = new ProgressEventArgs(name, processed, target); - progressHandler(sender, args); - } - } - - /// - /// Initialise an instance of - /// - private StreamUtils() - { - // Do nothing. - } - } + /// + /// Read from a ensuring all the required data is read. + /// + /// The stream to read. + /// The buffer to fill. + /// + public static void ReadFully(Stream stream, byte[] buffer) + { + ReadFully(stream, buffer, 0, buffer.Length); + } + + /// + /// Read from a " ensuring all the required data is read. + /// + /// The stream to read data from. + /// The buffer to store data in. + /// The offset at which to begin storing data. + /// The number of bytes of data to store. + /// Required parameter is null + /// and or are invalid. + /// End of stream is encountered before all the data has been read. + public static void ReadFully(Stream stream, byte[] buffer, int offset, int count) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + // Offset can equal length when buffer and count are 0. + if ((offset < 0) || (offset > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if ((count < 0) || (offset + count > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + while (count > 0) + { + var readCount = stream.Read(buffer, offset, count); + if (readCount <= 0) + { + throw new EndOfStreamException(); + } + offset += readCount; + count -= readCount; + } + } + + /// + /// Read as much data as possible from a ", up to the requested number of bytes + /// + /// The stream to read data from. + /// The buffer to store data in. + /// The offset at which to begin storing data. + /// The number of bytes of data to store. + /// Required parameter is null + /// and or are invalid. + public static int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, int count) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + // Offset can equal length when buffer and count are 0. + if ((offset < 0) || (offset > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if ((count < 0) || (offset + count > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + var totalReadCount = 0; + while (count > 0) + { + var readCount = stream.Read(buffer, offset, count); + if (readCount <= 0) + { + break; + } + offset += readCount; + count -= readCount; + totalReadCount += readCount; + } + + return totalReadCount; + } + + /// + /// Copy the contents of one to another. + /// + /// The stream to source data from. + /// The stream to write data to. + /// The buffer to use during copying. + public static void Copy(Stream source, Stream destination, byte[] buffer) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (destination == null) + { + throw new ArgumentNullException(nameof(destination)); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + // Ensure a reasonable size of buffer is used without being prohibitive. + if (buffer.Length < 128) + { + throw new ArgumentException("Buffer is too small", nameof(buffer)); + } + + var copying = true; + + while (copying) + { + var bytesRead = source.Read(buffer, 0, buffer.Length); + if (bytesRead > 0) + { + destination.Write(buffer, 0, bytesRead); + } + else + { + destination.Flush(); + copying = false; + } + } + } + + /// + /// Copy the contents of one to another. + /// + /// The stream to source data from. + /// The stream to write data to. + /// The buffer to use during copying. + /// The progress handler delegate to use. + /// The minimum between progress updates. + /// The source for this event. + /// The name to use with the event. + /// This form is specialised for use within #Zip to support events during archive operations. + public static void Copy(Stream source, Stream destination, + byte[] buffer, ProgressHandler progressHandler, TimeSpan updateInterval, object sender, string name) + { + Copy(source, destination, buffer, progressHandler, updateInterval, sender, name, -1); + } + + /// + /// Copy the contents of one to another. + /// + /// The stream to source data from. + /// The stream to write data to. + /// The buffer to use during copying. + /// The progress handler delegate to use. + /// The minimum between progress updates. + /// The source for this event. + /// The name to use with the event. + /// A predetermined fixed target value to use with progress updates. + /// If the value is negative the target is calculated by looking at the stream. + /// This form is specialised for use within #Zip to support events during archive operations. + public static void Copy(Stream source, Stream destination, + byte[] buffer, + ProgressHandler progressHandler, TimeSpan updateInterval, + object sender, string name, long fixedTarget) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (destination == null) + { + throw new ArgumentNullException(nameof(destination)); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + // Ensure a reasonable size of buffer is used without being prohibitive. + if (buffer.Length < 128) + { + throw new ArgumentException("Buffer is too small", nameof(buffer)); + } + + if (progressHandler == null) + { + throw new ArgumentNullException(nameof(progressHandler)); + } + + var copying = true; + + var marker = DateTime.Now; + long processed = 0; + long target = 0; + + if (fixedTarget >= 0) + { + target = fixedTarget; + } + else if (source.CanSeek) + { + target = source.Length - source.Position; + } + + // Always fire 0% progress.. + var args = new ProgressEventArgs(name, processed, target); + progressHandler(sender, args); + + var progressFired = true; + + while (copying) + { + var bytesRead = source.Read(buffer, 0, buffer.Length); + if (bytesRead > 0) + { + processed += bytesRead; + progressFired = false; + destination.Write(buffer, 0, bytesRead); + } + else + { + destination.Flush(); + copying = false; + } + + if (DateTime.Now - marker > updateInterval) + { + progressFired = true; + marker = DateTime.Now; + args = new ProgressEventArgs(name, processed, target); + progressHandler(sender, args); + + copying = args.ContinueRunning; + } + } + + if (!progressFired) + { + args = new ProgressEventArgs(name, processed, target); + progressHandler(sender, args); + } + } + + /// + /// Initialise an instance of + /// + private StreamUtils() + { + // Do nothing. + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs index c4b4cb726..f4b0582c5 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs @@ -2,484 +2,483 @@ using System; using System.Security.Cryptography; -namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption +namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption; + +/// +/// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives. +/// While it has been superceded by more recent and more powerful algorithms, its still in use and +/// is viable for preventing casual snooping +/// +public abstract class PkzipClassic : SymmetricAlgorithm { - /// - /// PkzipClassic embodies the classic or original encryption facilities used in Pkzip archives. - /// While it has been superceded by more recent and more powerful algorithms, its still in use and - /// is viable for preventing casual snooping - /// - public abstract class PkzipClassic : SymmetricAlgorithm - { - /// - /// Generates new encryption keys based on given seed - /// - /// The seed value to initialise keys with. - /// A new key value. - static public byte[] GenerateKeys(byte[] seed) - { - if (seed == null) - { - throw new ArgumentNullException(nameof(seed)); - } - - if (seed.Length == 0) - { - throw new ArgumentException("Length is zero", nameof(seed)); - } - - uint[] newKeys = { - 0x12345678, - 0x23456789, - 0x34567890 - }; - - for (int i = 0; i < seed.Length; ++i) - { - newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]); - newKeys[1] = newKeys[1] + (byte)newKeys[0]; - newKeys[1] = newKeys[1] * 134775813 + 1; - newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24)); - } - - byte[] result = new byte[12]; - result[0] = (byte)(newKeys[0] & 0xff); - result[1] = (byte)((newKeys[0] >> 8) & 0xff); - result[2] = (byte)((newKeys[0] >> 16) & 0xff); - result[3] = (byte)((newKeys[0] >> 24) & 0xff); - result[4] = (byte)(newKeys[1] & 0xff); - result[5] = (byte)((newKeys[1] >> 8) & 0xff); - result[6] = (byte)((newKeys[1] >> 16) & 0xff); - result[7] = (byte)((newKeys[1] >> 24) & 0xff); - result[8] = (byte)(newKeys[2] & 0xff); - result[9] = (byte)((newKeys[2] >> 8) & 0xff); - result[10] = (byte)((newKeys[2] >> 16) & 0xff); - result[11] = (byte)((newKeys[2] >> 24) & 0xff); - return result; - } - } - - /// - /// PkzipClassicCryptoBase provides the low level facilities for encryption - /// and decryption using the PkzipClassic algorithm. - /// - internal class PkzipClassicCryptoBase - { - /// - /// Transform a single byte - /// - /// - /// The transformed value - /// - protected byte TransformByte() - { - uint temp = ((keys[2] & 0xFFFF) | 2); - return (byte)((temp * (temp ^ 1)) >> 8); - } - - /// - /// Set the key schedule for encryption/decryption. - /// - /// The data use to set the keys from. - protected void SetKeys(byte[] keyData) - { - if (keyData == null) - { - throw new ArgumentNullException(nameof(keyData)); - } - - if (keyData.Length != 12) - { - throw new InvalidOperationException("Key length is not valid"); - } - - keys = new uint[3]; - keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]); - keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]); - keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]); - } - - /// - /// Update encryption keys - /// - protected void UpdateKeys(byte ch) - { - keys[0] = Crc32.ComputeCrc32(keys[0], ch); - keys[1] = keys[1] + (byte)keys[0]; - keys[1] = keys[1] * 134775813 + 1; - keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); - } - - /// - /// Reset the internal state. - /// - protected void Reset() - { - keys[0] = 0; - keys[1] = 0; - keys[2] = 0; - } - - #region Instance Fields - - private uint[] keys; - - #endregion Instance Fields - } - - /// - /// PkzipClassic CryptoTransform for encryption. - /// - internal class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform - { - /// - /// Initialise a new instance of - /// - /// The key block to use. - internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock) - { - SetKeys(keyBlock); - } - - #region ICryptoTransform Members - - /// - /// Transforms the specified region of the specified byte array. - /// - /// The input for which to compute the transform. - /// The offset into the byte array from which to begin using data. - /// The number of bytes in the byte array to use as data. - /// The computed transform. - public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - byte[] result = new byte[inputCount]; - TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); - return result; - } - - /// - /// Transforms the specified region of the input byte array and copies - /// the resulting transform to the specified region of the output byte array. - /// - /// The input for which to compute the transform. - /// The offset into the input byte array from which to begin using data. - /// The number of bytes in the input byte array to use as data. - /// The output to which to write the transform. - /// The offset into the output byte array from which to begin writing data. - /// The number of bytes written. - public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) - { - for (int i = inputOffset; i < inputOffset + inputCount; ++i) - { - byte oldbyte = inputBuffer[i]; - outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte()); - UpdateKeys(oldbyte); - } - return inputCount; - } - - /// - /// Gets a value indicating whether the current transform can be reused. - /// - public bool CanReuseTransform - { - get - { - return true; - } - } - - /// - /// Gets the size of the input data blocks in bytes. - /// - public int InputBlockSize - { - get - { - return 1; - } - } - - /// - /// Gets the size of the output data blocks in bytes. - /// - public int OutputBlockSize - { - get - { - return 1; - } - } - - /// - /// Gets a value indicating whether multiple blocks can be transformed. - /// - public bool CanTransformMultipleBlocks - { - get - { - return true; - } - } - - #endregion ICryptoTransform Members - - #region IDisposable Members - - /// - /// Cleanup internal state. - /// - public void Dispose() - { - Reset(); - } - - #endregion IDisposable Members - } - - /// - /// PkzipClassic CryptoTransform for decryption. - /// - internal class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform - { - /// - /// Initialise a new instance of . - /// - /// The key block to decrypt with. - internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock) - { - SetKeys(keyBlock); - } - - #region ICryptoTransform Members - - /// - /// Transforms the specified region of the specified byte array. - /// - /// The input for which to compute the transform. - /// The offset into the byte array from which to begin using data. - /// The number of bytes in the byte array to use as data. - /// The computed transform. - public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - byte[] result = new byte[inputCount]; - TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); - return result; - } - - /// - /// Transforms the specified region of the input byte array and copies - /// the resulting transform to the specified region of the output byte array. - /// - /// The input for which to compute the transform. - /// The offset into the input byte array from which to begin using data. - /// The number of bytes in the input byte array to use as data. - /// The output to which to write the transform. - /// The offset into the output byte array from which to begin writing data. - /// The number of bytes written. - public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) - { - for (int i = inputOffset; i < inputOffset + inputCount; ++i) - { - var newByte = (byte)(inputBuffer[i] ^ TransformByte()); - outputBuffer[outputOffset++] = newByte; - UpdateKeys(newByte); - } - return inputCount; - } - - /// - /// Gets a value indicating whether the current transform can be reused. - /// - public bool CanReuseTransform - { - get - { - return true; - } - } - - /// - /// Gets the size of the input data blocks in bytes. - /// - public int InputBlockSize - { - get - { - return 1; - } - } - - /// - /// Gets the size of the output data blocks in bytes. - /// - public int OutputBlockSize - { - get - { - return 1; - } - } - - /// - /// Gets a value indicating whether multiple blocks can be transformed. - /// - public bool CanTransformMultipleBlocks - { - get - { - return true; - } - } - - #endregion ICryptoTransform Members - - #region IDisposable Members - - /// - /// Cleanup internal state. - /// - public void Dispose() - { - Reset(); - } - - #endregion IDisposable Members - } - - /// - /// Defines a wrapper object to access the Pkzip algorithm. - /// This class cannot be inherited. - /// - public sealed class PkzipClassicManaged : PkzipClassic - { - /// - /// Get / set the applicable block size in bits. - /// - /// The only valid block size is 8. - public override int BlockSize - { - get - { - return 8; - } - - set - { - if (value != 8) - { - throw new CryptographicException("Block size is invalid"); - } - } - } - - /// - /// Get an array of legal key sizes. - /// - public override KeySizes[] LegalKeySizes - { - get - { - KeySizes[] keySizes = new KeySizes[1]; - keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0); - return keySizes; - } - } - - /// - /// Generate an initial vector. - /// - public override void GenerateIV() - { - // Do nothing. - } - - /// - /// Get an array of legal block sizes. - /// - public override KeySizes[] LegalBlockSizes - { - get - { - KeySizes[] keySizes = new KeySizes[1]; - keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0); - return keySizes; - } - } - - /// - /// Get / set the key value applicable. - /// - public override byte[] Key - { - get - { - if (key_ == null) - { - GenerateKey(); - } - - return (byte[])key_.Clone(); - } - - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (value.Length != 12) - { - throw new CryptographicException("Key size is illegal"); - } - - key_ = (byte[])value.Clone(); - } - } - - /// - /// Generate a new random key. - /// - public override void GenerateKey() - { - key_ = new byte[12]; - var rng = RandomNumberGenerator.Create(); - rng.GetBytes(key_); - } - - /// - /// Create an encryptor. - /// - /// The key to use for this encryptor. - /// Initialisation vector for the new encryptor. - /// Returns a new PkzipClassic encryptor - public override ICryptoTransform CreateEncryptor( - byte[] rgbKey, - byte[] rgbIV) - { - key_ = rgbKey; - return new PkzipClassicEncryptCryptoTransform(Key); - } - - /// - /// Create a decryptor. - /// - /// Keys to use for this new decryptor. - /// Initialisation vector for the new decryptor. - /// Returns a new decryptor. - public override ICryptoTransform CreateDecryptor( - byte[] rgbKey, - byte[] rgbIV) - { - key_ = rgbKey; - return new PkzipClassicDecryptCryptoTransform(Key); - } - - #region Instance Fields - - private byte[] key_; - - #endregion Instance Fields - } + /// + /// Generates new encryption keys based on given seed + /// + /// The seed value to initialise keys with. + /// A new key value. + public static byte[] GenerateKeys(byte[] seed) + { + if (seed == null) + { + throw new ArgumentNullException(nameof(seed)); + } + + if (seed.Length == 0) + { + throw new ArgumentException("Length is zero", nameof(seed)); + } + + uint[] newKeys = { + 0x12345678, + 0x23456789, + 0x34567890 + }; + + for (var i = 0; i < seed.Length; ++i) + { + newKeys[0] = Crc32.ComputeCrc32(newKeys[0], seed[i]); + newKeys[1] = newKeys[1] + (byte)newKeys[0]; + newKeys[1] = (newKeys[1] * 134775813) + 1; + newKeys[2] = Crc32.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24)); + } + + var result = new byte[12]; + result[0] = (byte)(newKeys[0] & 0xff); + result[1] = (byte)((newKeys[0] >> 8) & 0xff); + result[2] = (byte)((newKeys[0] >> 16) & 0xff); + result[3] = (byte)((newKeys[0] >> 24) & 0xff); + result[4] = (byte)(newKeys[1] & 0xff); + result[5] = (byte)((newKeys[1] >> 8) & 0xff); + result[6] = (byte)((newKeys[1] >> 16) & 0xff); + result[7] = (byte)((newKeys[1] >> 24) & 0xff); + result[8] = (byte)(newKeys[2] & 0xff); + result[9] = (byte)((newKeys[2] >> 8) & 0xff); + result[10] = (byte)((newKeys[2] >> 16) & 0xff); + result[11] = (byte)((newKeys[2] >> 24) & 0xff); + return result; + } +} + +/// +/// PkzipClassicCryptoBase provides the low level facilities for encryption +/// and decryption using the PkzipClassic algorithm. +/// +internal class PkzipClassicCryptoBase +{ + /// + /// Transform a single byte + /// + /// + /// The transformed value + /// + protected byte TransformByte() + { + var temp = (keys[2] & 0xFFFF) | 2; + return (byte)((temp * (temp ^ 1)) >> 8); + } + + /// + /// Set the key schedule for encryption/decryption. + /// + /// The data use to set the keys from. + protected void SetKeys(byte[] keyData) + { + if (keyData == null) + { + throw new ArgumentNullException(nameof(keyData)); + } + + if (keyData.Length != 12) + { + throw new InvalidOperationException("Key length is not valid"); + } + + keys = new uint[3]; + keys[0] = (uint)((keyData[3] << 24) | (keyData[2] << 16) | (keyData[1] << 8) | keyData[0]); + keys[1] = (uint)((keyData[7] << 24) | (keyData[6] << 16) | (keyData[5] << 8) | keyData[4]); + keys[2] = (uint)((keyData[11] << 24) | (keyData[10] << 16) | (keyData[9] << 8) | keyData[8]); + } + + /// + /// Update encryption keys + /// + protected void UpdateKeys(byte ch) + { + keys[0] = Crc32.ComputeCrc32(keys[0], ch); + keys[1] = keys[1] + (byte)keys[0]; + keys[1] = (keys[1] * 134775813) + 1; + keys[2] = Crc32.ComputeCrc32(keys[2], (byte)(keys[1] >> 24)); + } + + /// + /// Reset the internal state. + /// + protected void Reset() + { + keys[0] = 0; + keys[1] = 0; + keys[2] = 0; + } + + #region Instance Fields + + private uint[] keys; + + #endregion Instance Fields +} + +/// +/// PkzipClassic CryptoTransform for encryption. +/// +internal class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform +{ + /// + /// Initialise a new instance of + /// + /// The key block to use. + internal PkzipClassicEncryptCryptoTransform(byte[] keyBlock) + { + SetKeys(keyBlock); + } + + #region ICryptoTransform Members + + /// + /// Transforms the specified region of the specified byte array. + /// + /// The input for which to compute the transform. + /// The offset into the byte array from which to begin using data. + /// The number of bytes in the byte array to use as data. + /// The computed transform. + public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) + { + var result = new byte[inputCount]; + TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); + return result; + } + + /// + /// Transforms the specified region of the input byte array and copies + /// the resulting transform to the specified region of the output byte array. + /// + /// The input for which to compute the transform. + /// The offset into the input byte array from which to begin using data. + /// The number of bytes in the input byte array to use as data. + /// The output to which to write the transform. + /// The offset into the output byte array from which to begin writing data. + /// The number of bytes written. + public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) + { + for (var i = inputOffset; i < inputOffset + inputCount; ++i) + { + var oldbyte = inputBuffer[i]; + outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte()); + UpdateKeys(oldbyte); + } + return inputCount; + } + + /// + /// Gets a value indicating whether the current transform can be reused. + /// + public bool CanReuseTransform + { + get + { + return true; + } + } + + /// + /// Gets the size of the input data blocks in bytes. + /// + public int InputBlockSize + { + get + { + return 1; + } + } + + /// + /// Gets the size of the output data blocks in bytes. + /// + public int OutputBlockSize + { + get + { + return 1; + } + } + + /// + /// Gets a value indicating whether multiple blocks can be transformed. + /// + public bool CanTransformMultipleBlocks + { + get + { + return true; + } + } + + #endregion ICryptoTransform Members + + #region IDisposable Members + + /// + /// Cleanup internal state. + /// + public void Dispose() + { + Reset(); + } + + #endregion IDisposable Members +} + +/// +/// PkzipClassic CryptoTransform for decryption. +/// +internal class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform +{ + /// + /// Initialise a new instance of . + /// + /// The key block to decrypt with. + internal PkzipClassicDecryptCryptoTransform(byte[] keyBlock) + { + SetKeys(keyBlock); + } + + #region ICryptoTransform Members + + /// + /// Transforms the specified region of the specified byte array. + /// + /// The input for which to compute the transform. + /// The offset into the byte array from which to begin using data. + /// The number of bytes in the byte array to use as data. + /// The computed transform. + public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) + { + var result = new byte[inputCount]; + TransformBlock(inputBuffer, inputOffset, inputCount, result, 0); + return result; + } + + /// + /// Transforms the specified region of the input byte array and copies + /// the resulting transform to the specified region of the output byte array. + /// + /// The input for which to compute the transform. + /// The offset into the input byte array from which to begin using data. + /// The number of bytes in the input byte array to use as data. + /// The output to which to write the transform. + /// The offset into the output byte array from which to begin writing data. + /// The number of bytes written. + public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) + { + for (var i = inputOffset; i < inputOffset + inputCount; ++i) + { + var newByte = (byte)(inputBuffer[i] ^ TransformByte()); + outputBuffer[outputOffset++] = newByte; + UpdateKeys(newByte); + } + return inputCount; + } + + /// + /// Gets a value indicating whether the current transform can be reused. + /// + public bool CanReuseTransform + { + get + { + return true; + } + } + + /// + /// Gets the size of the input data blocks in bytes. + /// + public int InputBlockSize + { + get + { + return 1; + } + } + + /// + /// Gets the size of the output data blocks in bytes. + /// + public int OutputBlockSize + { + get + { + return 1; + } + } + + /// + /// Gets a value indicating whether multiple blocks can be transformed. + /// + public bool CanTransformMultipleBlocks + { + get + { + return true; + } + } + + #endregion ICryptoTransform Members + + #region IDisposable Members + + /// + /// Cleanup internal state. + /// + public void Dispose() + { + Reset(); + } + + #endregion IDisposable Members +} + +/// +/// Defines a wrapper object to access the Pkzip algorithm. +/// This class cannot be inherited. +/// +public sealed class PkzipClassicManaged : PkzipClassic +{ + /// + /// Get / set the applicable block size in bits. + /// + /// The only valid block size is 8. + public override int BlockSize + { + get + { + return 8; + } + + set + { + if (value != 8) + { + throw new CryptographicException("Block size is invalid"); + } + } + } + + /// + /// Get an array of legal key sizes. + /// + public override KeySizes[] LegalKeySizes + { + get + { + var keySizes = new KeySizes[1]; + keySizes[0] = new KeySizes(12 * 8, 12 * 8, 0); + return keySizes; + } + } + + /// + /// Generate an initial vector. + /// + public override void GenerateIV() + { + // Do nothing. + } + + /// + /// Get an array of legal block sizes. + /// + public override KeySizes[] LegalBlockSizes + { + get + { + var keySizes = new KeySizes[1]; + keySizes[0] = new KeySizes(1 * 8, 1 * 8, 0); + return keySizes; + } + } + + /// + /// Get / set the key value applicable. + /// + public override byte[] Key + { + get + { + if (key_ == null) + { + GenerateKey(); + } + + return (byte[])key_.Clone(); + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length != 12) + { + throw new CryptographicException("Key size is illegal"); + } + + key_ = (byte[])value.Clone(); + } + } + + /// + /// Generate a new random key. + /// + public override void GenerateKey() + { + key_ = new byte[12]; + var rng = RandomNumberGenerator.Create(); + rng.GetBytes(key_); + } + + /// + /// Create an encryptor. + /// + /// The key to use for this encryptor. + /// Initialisation vector for the new encryptor. + /// Returns a new PkzipClassic encryptor + public override ICryptoTransform CreateEncryptor( + byte[] rgbKey, + byte[] rgbIV) + { + key_ = rgbKey; + return new PkzipClassicEncryptCryptoTransform(Key); + } + + /// + /// Create a decryptor. + /// + /// Keys to use for this new decryptor. + /// Initialisation vector for the new decryptor. + /// Returns a new decryptor. + public override ICryptoTransform CreateDecryptor( + byte[] rgbKey, + byte[] rgbIV) + { + key_ = rgbKey; + return new PkzipClassicDecryptCryptoTransform(Key); + } + + #region Instance Fields + + private byte[] key_; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs index 9d45c2c58..ea7531755 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs @@ -1,232 +1,231 @@ -using System; -using System.IO; -using System.Security.Cryptography; //using System.Threading; //using System.Threading.Tasks; using MelonLoader.ICSharpCode.SharpZipLib.Core; using MelonLoader.ICSharpCode.SharpZipLib.Zip; +using System; +using System.IO; +using System.Security.Cryptography; + +namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption; -namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption +/// +/// Encrypts and decrypts AES ZIP +/// +/// +/// Based on information from http://www.winzip.com/aes_info.htm +/// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/ +/// +internal class ZipAESStream : CryptoStream { - /// - /// Encrypts and decrypts AES ZIP - /// - /// - /// Based on information from http://www.winzip.com/aes_info.htm - /// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/ - /// - internal class ZipAESStream : CryptoStream - { - /// - /// Constructor - /// - /// The stream on which to perform the cryptographic transformation. - /// Instance of ZipAESTransform - /// Read or Write - public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode) - : base(stream, transform, mode) - { - _stream = stream; - _transform = transform; - _slideBuffer = new byte[1024]; - - // mode: - // CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method. - // Write bypasses this stream and uses the Transform directly. - if (mode != CryptoStreamMode.Read) - { - throw new Exception("ZipAESStream only for read"); - } - } - - // The final n bytes of the AES stream contain the Auth Code. - private const int AUTH_CODE_LENGTH = 10; - - // Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32. - private const int CRYPTO_BLOCK_SIZE = 16; - - // total length of block + auth code - private const int BLOCK_AND_AUTH = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH; - - private Stream _stream; - private ZipAESTransform _transform; - private byte[] _slideBuffer; - private int _slideBufStartPos; - private int _slideBufFreePos; - - // Buffer block transforms to enable partial reads - private byte[] _transformBuffer = null;// new byte[CRYPTO_BLOCK_SIZE]; - private int _transformBufferFreePos; - private int _transformBufferStartPos; - - // Do we have some buffered data available? - private bool HasBufferedData =>_transformBuffer != null && _transformBufferStartPos < _transformBufferFreePos; - - /// - /// Reads a sequence of bytes from the current CryptoStream into buffer, - /// and advances the position within the stream by the number of bytes read. - /// - public override int Read(byte[] buffer, int offset, int count) - { - // Nothing to do - if (count == 0) - return 0; - - // If we have buffered data, read that first - int nBytes = 0; - if (HasBufferedData) - { - nBytes = ReadBufferedData(buffer, offset, count); - - // Read all requested data from the buffer - if (nBytes == count) - return nBytes; - - offset += nBytes; - count -= nBytes; - } - - // Read more data from the input, if available - if (_slideBuffer != null) - nBytes += ReadAndTransform(buffer, offset, count); - - return nBytes; - } - - /// - /* - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var readCount = Read(buffer, offset, count); - return Task.FromResult(readCount); - } - */ - - // Read data from the underlying stream and decrypt it - private int ReadAndTransform(byte[] buffer, int offset, int count) - { - int nBytes = 0; - while (nBytes < count) - { - int bytesLeftToRead = count - nBytes; - - // Calculate buffer quantities vs read-ahead size, and check for sufficient free space - int byteCount = _slideBufFreePos - _slideBufStartPos; - - // Need to handle final block and Auth Code specially, but don't know total data length. - // Maintain a read-ahead equal to the length of (crypto block + Auth Code). - // When that runs out we can detect these final sections. - int lengthToRead = BLOCK_AND_AUTH - byteCount; - if (_slideBuffer.Length - _slideBufFreePos < lengthToRead) - { - // Shift the data to the beginning of the buffer - int iTo = 0; - for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++) - { - _slideBuffer[iTo] = _slideBuffer[iFrom]; - } - _slideBufFreePos -= _slideBufStartPos; // Note the -= - _slideBufStartPos = 0; - } - int obtained = StreamUtils.ReadRequestedBytes(_stream, _slideBuffer, _slideBufFreePos, lengthToRead); - _slideBufFreePos += obtained; - - // Recalculate how much data we now have - byteCount = _slideBufFreePos - _slideBufStartPos; - if (byteCount >= BLOCK_AND_AUTH) - { - var read = TransformAndBufferBlock(buffer, offset, bytesLeftToRead, CRYPTO_BLOCK_SIZE); - nBytes += read; - offset += read; - } - else - { - // Last round. - if (byteCount > AUTH_CODE_LENGTH) - { - // At least one byte of data plus auth code - int finalBlock = byteCount - AUTH_CODE_LENGTH; - nBytes += TransformAndBufferBlock(buffer, offset, bytesLeftToRead, finalBlock); - } - else if (byteCount < AUTH_CODE_LENGTH) - throw new ZipException("Internal error missed auth code"); // Coding bug - // Final block done. Check Auth code. - byte[] calcAuthCode = _transform.GetAuthCode(); - for (int i = 0; i < AUTH_CODE_LENGTH; i++) - { - if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i]) - { - throw new ZipException("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n" - + "The file may be damaged."); - } - } - - // don't need this any more, so use it as a 'complete' flag - _slideBuffer = null; - - break; // Reached the auth code - } - } - return nBytes; - } - - // read some buffered data - private int ReadBufferedData(byte[] buffer, int offset, int count) - { - int copyCount = Math.Min(count, _transformBufferFreePos - _transformBufferStartPos); - - Array.Copy(_transformBuffer, _transformBufferStartPos, buffer, offset, copyCount); - _transformBufferStartPos += copyCount; - - return copyCount; - } - - // Perform the crypto transform, and buffer the data if less than one block has been requested. - private int TransformAndBufferBlock(byte[] buffer, int offset, int count, int blockSize) - { - // If the requested data is greater than one block, transform it directly into the output - // If it's smaller, do it into a temporary buffer and copy the requested part - bool bufferRequired = (blockSize > count); - - if (bufferRequired && _transformBuffer == null) - _transformBuffer = new byte[CRYPTO_BLOCK_SIZE]; - - var targetBuffer = bufferRequired ? _transformBuffer : buffer; - var targetOffset = bufferRequired ? 0 : offset; - - // Transform the data - _transform.TransformBlock(_slideBuffer, - _slideBufStartPos, - blockSize, - targetBuffer, - targetOffset); - - _slideBufStartPos += blockSize; - - if (!bufferRequired) - { - return blockSize; - } - else - { - Array.Copy(_transformBuffer, 0, buffer, offset, count); - _transformBufferStartPos = count; - _transformBufferFreePos = blockSize; - - return count; - } - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies count bytes from buffer to the current stream. - /// The byte offset in buffer at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - public override void Write(byte[] buffer, int offset, int count) - { - // ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly. - throw new NotImplementedException(); - } - } + /// + /// Constructor + /// + /// The stream on which to perform the cryptographic transformation. + /// Instance of ZipAESTransform + /// Read or Write + public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode) + : base(stream, transform, mode) + { + _stream = stream; + _transform = transform; + _slideBuffer = new byte[1024]; + + // mode: + // CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method. + // Write bypasses this stream and uses the Transform directly. + if (mode != CryptoStreamMode.Read) + { + throw new Exception("ZipAESStream only for read"); + } + } + + // The final n bytes of the AES stream contain the Auth Code. + private const int AUTH_CODE_LENGTH = 10; + + // Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32. + private const int CRYPTO_BLOCK_SIZE = 16; + + // total length of block + auth code + private const int BLOCK_AND_AUTH = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH; + + private readonly Stream _stream; + private readonly ZipAESTransform _transform; + private byte[] _slideBuffer; + private int _slideBufStartPos; + private int _slideBufFreePos; + + // Buffer block transforms to enable partial reads + private byte[] _transformBuffer = null;// new byte[CRYPTO_BLOCK_SIZE]; + private int _transformBufferFreePos; + private int _transformBufferStartPos; + + // Do we have some buffered data available? + private bool HasBufferedData => _transformBuffer != null && _transformBufferStartPos < _transformBufferFreePos; + + /// + /// Reads a sequence of bytes from the current CryptoStream into buffer, + /// and advances the position within the stream by the number of bytes read. + /// + public override int Read(byte[] buffer, int offset, int count) + { + // Nothing to do + if (count == 0) + return 0; + + // If we have buffered data, read that first + var nBytes = 0; + if (HasBufferedData) + { + nBytes = ReadBufferedData(buffer, offset, count); + + // Read all requested data from the buffer + if (nBytes == count) + return nBytes; + + offset += nBytes; + count -= nBytes; + } + + // Read more data from the input, if available + if (_slideBuffer != null) + nBytes += ReadAndTransform(buffer, offset, count); + + return nBytes; + } + + /// + /* + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var readCount = Read(buffer, offset, count); + return Task.FromResult(readCount); + } + */ + + // Read data from the underlying stream and decrypt it + private int ReadAndTransform(byte[] buffer, int offset, int count) + { + var nBytes = 0; + while (nBytes < count) + { + var bytesLeftToRead = count - nBytes; + + // Calculate buffer quantities vs read-ahead size, and check for sufficient free space + var byteCount = _slideBufFreePos - _slideBufStartPos; + + // Need to handle final block and Auth Code specially, but don't know total data length. + // Maintain a read-ahead equal to the length of (crypto block + Auth Code). + // When that runs out we can detect these final sections. + var lengthToRead = BLOCK_AND_AUTH - byteCount; + if (_slideBuffer.Length - _slideBufFreePos < lengthToRead) + { + // Shift the data to the beginning of the buffer + var iTo = 0; + for (var iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++) + { + _slideBuffer[iTo] = _slideBuffer[iFrom]; + } + _slideBufFreePos -= _slideBufStartPos; // Note the -= + _slideBufStartPos = 0; + } + var obtained = StreamUtils.ReadRequestedBytes(_stream, _slideBuffer, _slideBufFreePos, lengthToRead); + _slideBufFreePos += obtained; + + // Recalculate how much data we now have + byteCount = _slideBufFreePos - _slideBufStartPos; + if (byteCount >= BLOCK_AND_AUTH) + { + var read = TransformAndBufferBlock(buffer, offset, bytesLeftToRead, CRYPTO_BLOCK_SIZE); + nBytes += read; + offset += read; + } + else + { + // Last round. + if (byteCount > AUTH_CODE_LENGTH) + { + // At least one byte of data plus auth code + var finalBlock = byteCount - AUTH_CODE_LENGTH; + nBytes += TransformAndBufferBlock(buffer, offset, bytesLeftToRead, finalBlock); + } + else if (byteCount < AUTH_CODE_LENGTH) + throw new ZipException("Internal error missed auth code"); // Coding bug + // Final block done. Check Auth code. + var calcAuthCode = _transform.GetAuthCode(); + for (var i = 0; i < AUTH_CODE_LENGTH; i++) + { + if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i]) + { + throw new ZipException("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n" + + "The file may be damaged."); + } + } + + // don't need this any more, so use it as a 'complete' flag + _slideBuffer = null; + + break; // Reached the auth code + } + } + return nBytes; + } + + // read some buffered data + private int ReadBufferedData(byte[] buffer, int offset, int count) + { + var copyCount = Math.Min(count, _transformBufferFreePos - _transformBufferStartPos); + + Array.Copy(_transformBuffer, _transformBufferStartPos, buffer, offset, copyCount); + _transformBufferStartPos += copyCount; + + return copyCount; + } + + // Perform the crypto transform, and buffer the data if less than one block has been requested. + private int TransformAndBufferBlock(byte[] buffer, int offset, int count, int blockSize) + { + // If the requested data is greater than one block, transform it directly into the output + // If it's smaller, do it into a temporary buffer and copy the requested part + var bufferRequired = blockSize > count; + + if (bufferRequired && _transformBuffer == null) + _transformBuffer = new byte[CRYPTO_BLOCK_SIZE]; + + var targetBuffer = bufferRequired ? _transformBuffer : buffer; + var targetOffset = bufferRequired ? 0 : offset; + + // Transform the data + _transform.TransformBlock(_slideBuffer, + _slideBufStartPos, + blockSize, + targetBuffer, + targetOffset); + + _slideBufStartPos += blockSize; + + if (!bufferRequired) + { + return blockSize; + } + else + { + Array.Copy(_transformBuffer, 0, buffer, offset, count); + _transformBufferStartPos = count; + _transformBufferFreePos = blockSize; + + return count; + } + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + // ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly. + throw new NotImplementedException(); + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs index 82bac2a93..e5694ccab 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs @@ -1,222 +1,216 @@ +using MelonLoader.ICSharpCode.SharpZipLib.Core; using System; using System.Security.Cryptography; -using MelonLoader.ICSharpCode.SharpZipLib.Core; -namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption +namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption; + +/// +/// Transforms stream using AES in CTR mode +/// +internal class ZipAESTransform : ICryptoTransform { - /// - /// Transforms stream using AES in CTR mode - /// - internal class ZipAESTransform : ICryptoTransform - { - class IncrementalHash : HMACSHA1 - { - bool _finalised; - public IncrementalHash(byte[] key) : base(key) { } - public static IncrementalHash CreateHMAC(string n, byte[] key) => new IncrementalHash(key); - public void AppendData(byte[] buffer, int offset, int count) => TransformBlock(buffer, offset, count, buffer, offset); - public byte[] GetHashAndReset() - { - if (!_finalised) - { - byte[] dummy = new byte[0]; - TransformFinalBlock(dummy, 0, 0); - _finalised = true; - } - return Hash; - } - } - - static class HashAlgorithmName - { - public static string SHA1 = null; - } - - private const int PWD_VER_LENGTH = 2; - - // WinZip use iteration count of 1000 for PBKDF2 key generation - private const int KEY_ROUNDS = 1000; - - // For 128-bit AES (16 bytes) the encryption is implemented as expected. - // For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption - // block but use only the first 16 bytes of it, and discard the second half. - private const int ENCRYPT_BLOCK = 16; - - private int _blockSize; - private readonly ICryptoTransform _encryptor; - private readonly byte[] _counterNonce; - private byte[] _encryptBuffer; - private int _encrPos; - private byte[] _pwdVerifier; - private IncrementalHash _hmacsha1; - private byte[] _authCode = null; - - private bool _writeMode; - - /// - /// Constructor. - /// - /// Password string - /// Random bytes, length depends on encryption strength. - /// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. - /// The encryption strength, in bytes eg 16 for 128 bits. - /// True when creating a zip, false when reading. For the AuthCode. - /// - public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode) - { - if (blockSize != 16 && blockSize != 32) // 24 valid for AES but not supported by Winzip - throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32."); - if (saltBytes.Length != blockSize / 2) - throw new Exception("Invalid salt len. Must be " + blockSize / 2 + " for blocksize " + blockSize); - // initialise the encryption buffer and buffer pos - _blockSize = blockSize; - _encryptBuffer = new byte[_blockSize]; - _encrPos = ENCRYPT_BLOCK; - - // Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c - var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS); - var rm = Aes.Create(); - rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode - _counterNonce = new byte[_blockSize]; - byte[] key1bytes = pdb.GetBytes(_blockSize); - byte[] key2bytes = pdb.GetBytes(_blockSize); - - // Use empty IV for AES - _encryptor = rm.CreateEncryptor(key1bytes, new byte[16]); - _pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH); - // - _hmacsha1 = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key2bytes); - _writeMode = writeMode; - } - - /// - /// Implement the ICryptoTransform method. - /// - public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) - { - // Pass the data stream to the hash algorithm for generating the Auth Code. - // This does not change the inputBuffer. Do this before decryption for read mode. - if (!_writeMode) - { - _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount); - } - // Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this. - int ix = 0; - while (ix < inputCount) - { - if (_encrPos == ENCRYPT_BLOCK) - { - /* increment encryption nonce */ - int j = 0; - while (++_counterNonce[j] == 0) - { - ++j; - } - /* encrypt the nonce to form next xor buffer */ - _encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0); - _encrPos = 0; - } - outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]); - // - ix++; - } - if (_writeMode) - { - // This does not change the buffer. - _hmacsha1.AppendData(outputBuffer, outputOffset, inputCount); - } - return inputCount; - } - - /// - /// Returns the 2 byte password verifier - /// - public byte[] PwdVerifier - { - get - { - return _pwdVerifier; - } - } - - /// - /// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream. - /// - public byte[] GetAuthCode() - { - if (_authCode == null) - { - _authCode = _hmacsha1.GetHashAndReset(); - } - return _authCode; - } - - #region ICryptoTransform Members - - /// - /// Not implemented. - /// - public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - if(inputCount > 0) - { - throw new NotImplementedException("TransformFinalBlock is not implemented and inputCount is greater than 0"); - } - return Empty.Array(); - } - - /// - /// Gets the size of the input data blocks in bytes. - /// - public int InputBlockSize - { - get - { - return _blockSize; - } - } - - /// - /// Gets the size of the output data blocks in bytes. - /// - public int OutputBlockSize - { - get - { - return _blockSize; - } - } - - /// - /// Gets a value indicating whether multiple blocks can be transformed. - /// - public bool CanTransformMultipleBlocks - { - get - { - return true; - } - } - - /// - /// Gets a value indicating whether the current transform can be reused. - /// - public bool CanReuseTransform - { - get - { - return true; - } - } - - /// - /// Cleanup internal state. - /// - public void Dispose() - { - _encryptor.Dispose(); - } - - #endregion ICryptoTransform Members - } + private class IncrementalHash : HMACSHA1 + { + private bool _finalised; + public IncrementalHash(byte[] key) : base(key) { } + public static IncrementalHash CreateHMAC(string n, byte[] key) => new(key); + public void AppendData(byte[] buffer, int offset, int count) => TransformBlock(buffer, offset, count, buffer, offset); + public byte[] GetHashAndReset() + { + if (!_finalised) + { + var dummy = new byte[0]; + TransformFinalBlock(dummy, 0, 0); + _finalised = true; + } + return Hash; + } + } + + private static class HashAlgorithmName + { + public static string SHA1 = null; + } + + private const int PWD_VER_LENGTH = 2; + + // WinZip use iteration count of 1000 for PBKDF2 key generation + private const int KEY_ROUNDS = 1000; + + // For 128-bit AES (16 bytes) the encryption is implemented as expected. + // For 256-bit AES (32 bytes) WinZip do full 256 bit AES of the nonce to create the encryption + // block but use only the first 16 bytes of it, and discard the second half. + private const int ENCRYPT_BLOCK = 16; + + private readonly int _blockSize; + private readonly ICryptoTransform _encryptor; + private readonly byte[] _counterNonce; + private readonly byte[] _encryptBuffer; + private int _encrPos; + private readonly byte[] _pwdVerifier; + private readonly IncrementalHash _hmacsha1; + private byte[] _authCode = null; + + private readonly bool _writeMode; + + /// + /// Constructor. + /// + /// Password string + /// Random bytes, length depends on encryption strength. + /// 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. + /// The encryption strength, in bytes eg 16 for 128 bits. + /// True when creating a zip, false when reading. For the AuthCode. + /// + public ZipAESTransform(string key, byte[] saltBytes, int blockSize, bool writeMode) + { + if (blockSize is not 16 and not 32) // 24 valid for AES but not supported by Winzip + throw new Exception("Invalid blocksize " + blockSize + ". Must be 16 or 32."); + if (saltBytes.Length != blockSize / 2) + throw new Exception("Invalid salt len. Must be " + (blockSize / 2) + " for blocksize " + blockSize); + // initialise the encryption buffer and buffer pos + _blockSize = blockSize; + _encryptBuffer = new byte[_blockSize]; + _encrPos = ENCRYPT_BLOCK; + + // Performs the equivalent of derive_key in Dr Brian Gladman's pwd2key.c + var pdb = new Rfc2898DeriveBytes(key, saltBytes, KEY_ROUNDS); + var rm = Aes.Create(); + rm.Mode = CipherMode.ECB; // No feedback from cipher for CTR mode + _counterNonce = new byte[_blockSize]; + var key1bytes = pdb.GetBytes(_blockSize); + var key2bytes = pdb.GetBytes(_blockSize); + + // Use empty IV for AES + _encryptor = rm.CreateEncryptor(key1bytes, new byte[16]); + _pwdVerifier = pdb.GetBytes(PWD_VER_LENGTH); + // + _hmacsha1 = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, key2bytes); + _writeMode = writeMode; + } + + /// + /// Implement the ICryptoTransform method. + /// + public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) + { + // Pass the data stream to the hash algorithm for generating the Auth Code. + // This does not change the inputBuffer. Do this before decryption for read mode. + if (!_writeMode) + { + _hmacsha1.AppendData(inputBuffer, inputOffset, inputCount); + } + // Encrypt with AES in CTR mode. Regards to Dr Brian Gladman for this. + var ix = 0; + while (ix < inputCount) + { + if (_encrPos == ENCRYPT_BLOCK) + { + /* increment encryption nonce */ + var j = 0; + while (++_counterNonce[j] == 0) + { + ++j; + } + /* encrypt the nonce to form next xor buffer */ + _encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0); + _encrPos = 0; + } + outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]); + // + ix++; + } + if (_writeMode) + { + // This does not change the buffer. + _hmacsha1.AppendData(outputBuffer, outputOffset, inputCount); + } + return inputCount; + } + + /// + /// Returns the 2 byte password verifier + /// + public byte[] PwdVerifier + { + get + { + return _pwdVerifier; + } + } + + /// + /// Returns the 10 byte AUTH CODE to be checked or appended immediately following the AES data stream. + /// + public byte[] GetAuthCode() + { + _authCode ??= _hmacsha1.GetHashAndReset(); + return _authCode; + } + + #region ICryptoTransform Members + + /// + /// Not implemented. + /// + public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) + { + return inputCount > 0 + ? throw new NotImplementedException("TransformFinalBlock is not implemented and inputCount is greater than 0") + : Empty.Array(); + } + + /// + /// Gets the size of the input data blocks in bytes. + /// + public int InputBlockSize + { + get + { + return _blockSize; + } + } + + /// + /// Gets the size of the output data blocks in bytes. + /// + public int OutputBlockSize + { + get + { + return _blockSize; + } + } + + /// + /// Gets a value indicating whether multiple blocks can be transformed. + /// + public bool CanTransformMultipleBlocks + { + get + { + return true; + } + } + + /// + /// Gets a value indicating whether the current transform can be reused. + /// + public bool CanReuseTransform + { + get + { + return true; + } + } + + /// + /// Cleanup internal state. + /// + public void Dispose() + { + _encryptor.Dispose(); + } + + #endregion ICryptoTransform Members } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs index ea3720936..00160215e 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs @@ -1,78 +1,76 @@ using System; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.GZip -{ - /// - /// This class contains constants used for gzip. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] - sealed public class GZipConstants - { - /// - /// First GZip identification byte - /// - public const byte ID1 = 0x1F; +namespace MelonLoader.ICSharpCode.SharpZipLib.GZip; - /// - /// Second GZip identification byte - /// - public const byte ID2 = 0x8B; +/// +/// This class contains constants used for gzip. +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] +public sealed class GZipConstants +{ + /// + /// First GZip identification byte + /// + public const byte ID1 = 0x1F; - /// - /// Deflate compression method - /// - public const byte CompressionMethodDeflate = 0x8; + /// + /// Second GZip identification byte + /// + public const byte ID2 = 0x8B; - /// - /// Get the GZip specified encoding (CP-1252 if supported, otherwise ASCII) - /// - public static Encoding Encoding - { - get - { - try - { - return Encoding.GetEncoding(1252); - } - catch - { - return Encoding.ASCII; - } - } - } + /// + /// Deflate compression method + /// + public const byte CompressionMethodDeflate = 0x8; - } + /// + /// Get the GZip specified encoding (CP-1252 if supported, otherwise ASCII) + /// + public static Encoding Encoding + { + get + { + try + { + return Encoding.GetEncoding(1252); + } + catch + { + return Encoding.ASCII; + } + } + } +} - /// - /// GZip header flags - /// - [Flags] - public enum GZipFlags: byte - { - /// - /// Text flag hinting that the file is in ASCII - /// - FTEXT = 0x1 << 0, +/// +/// GZip header flags +/// +[Flags] +public enum GZipFlags : byte +{ + /// + /// Text flag hinting that the file is in ASCII + /// + FTEXT = 0x1 << 0, - /// - /// CRC flag indicating that a CRC16 preceeds the data - /// - FHCRC = 0x1 << 1, + /// + /// CRC flag indicating that a CRC16 preceeds the data + /// + FHCRC = 0x1 << 1, - /// - /// Extra flag indicating that extra fields are present - /// - FEXTRA = 0x1 << 2, + /// + /// Extra flag indicating that extra fields are present + /// + FEXTRA = 0x1 << 2, - /// - /// Filename flag indicating that the original filename is present - /// - FNAME = 0x1 << 3, + /// + /// Filename flag indicating that the original filename is present + /// + FNAME = 0x1 << 3, - /// - /// Flag bit mask indicating that a comment is present - /// - FCOMMENT = 0x1 << 4, - } + /// + /// Flag bit mask indicating that a comment is present + /// + FCOMMENT = 0x1 << 4, } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs index 64f9b2fcc..9e561e493 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs @@ -1,92 +1,86 @@ using System; using System.IO; +using static MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Deflater; -namespace MelonLoader.ICSharpCode.SharpZipLib.GZip -{ - using static Zip.Compression.Deflater; +namespace MelonLoader.ICSharpCode.SharpZipLib.GZip; - /// - /// An example class to demonstrate compression and decompression of GZip streams. - /// - public static class GZip - { - /// - /// Decompress the input writing - /// uncompressed data to the output stream - /// - /// The readable stream containing data to decompress. - /// The output stream to receive the decompressed data. - /// Both streams are closed on completion if true. - /// Input or output stream is null - public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner) - { - if (inStream == null) - throw new ArgumentNullException(nameof(inStream), "Input stream is null"); +/// +/// An example class to demonstrate compression and decompression of GZip streams. +/// +public static class GZip +{ + /// + /// Decompress the input writing + /// uncompressed data to the output stream + /// + /// The readable stream containing data to decompress. + /// The output stream to receive the decompressed data. + /// Both streams are closed on completion if true. + /// Input or output stream is null + public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner) + { + if (inStream == null) + throw new ArgumentNullException(nameof(inStream), "Input stream is null"); - if (outStream == null) - throw new ArgumentNullException(nameof(outStream), "Output stream is null"); + if (outStream == null) + throw new ArgumentNullException(nameof(outStream), "Output stream is null"); - try - { - using (GZipInputStream gzipInput = new GZipInputStream(inStream)) - { - gzipInput.IsStreamOwner = isStreamOwner; - Core.StreamUtils.Copy(gzipInput, outStream, new byte[4096]); - } - } - finally - { - if (isStreamOwner) - { - // inStream is closed by the GZipInputStream if stream owner - outStream.Dispose(); - } - } - } + try + { + using var gzipInput = new GZipInputStream(inStream); + gzipInput.IsStreamOwner = isStreamOwner; + Core.StreamUtils.Copy(gzipInput, outStream, new byte[4096]); + } + finally + { + if (isStreamOwner) + { + // inStream is closed by the GZipInputStream if stream owner + outStream.Dispose(); + } + } + } - /// - /// Compress the input stream sending - /// result data to output stream - /// - /// The readable stream to compress. - /// The output stream to receive the compressed data. - /// Both streams are closed on completion if true. - /// Deflate buffer size, minimum 512 - /// Deflate compression level, 0-9 - /// Input or output stream is null - /// Buffer Size is smaller than 512 - /// Compression level outside 0-9 - public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int bufferSize = 512, int level = 6) - { - if (inStream == null) - throw new ArgumentNullException(nameof(inStream), "Input stream is null"); + /// + /// Compress the input stream sending + /// result data to output stream + /// + /// The readable stream to compress. + /// The output stream to receive the compressed data. + /// Both streams are closed on completion if true. + /// Deflate buffer size, minimum 512 + /// Deflate compression level, 0-9 + /// Input or output stream is null + /// Buffer Size is smaller than 512 + /// Compression level outside 0-9 + public static void Compress(Stream inStream, Stream outStream, bool isStreamOwner, int bufferSize = 512, int level = 6) + { + if (inStream == null) + throw new ArgumentNullException(nameof(inStream), "Input stream is null"); - if (outStream == null) - throw new ArgumentNullException(nameof(outStream), "Output stream is null"); + if (outStream == null) + throw new ArgumentNullException(nameof(outStream), "Output stream is null"); - if (bufferSize < 512) - throw new ArgumentOutOfRangeException(nameof(bufferSize), "Deflate buffer size must be >= 512"); + if (bufferSize < 512) + throw new ArgumentOutOfRangeException(nameof(bufferSize), "Deflate buffer size must be >= 512"); - if (level < NO_COMPRESSION || level > BEST_COMPRESSION) - throw new ArgumentOutOfRangeException(nameof(level), "Compression level must be 0-9"); + if (level is < NO_COMPRESSION or > BEST_COMPRESSION) + throw new ArgumentOutOfRangeException(nameof(level), "Compression level must be 0-9"); - try - { - using (GZipOutputStream gzipOutput = new GZipOutputStream(outStream, bufferSize)) - { - gzipOutput.SetLevel(level); - gzipOutput.IsStreamOwner = isStreamOwner; - Core.StreamUtils.Copy(inStream, gzipOutput, new byte[bufferSize]); - } - } - finally - { - if (isStreamOwner) - { - // outStream is closed by the GZipOutputStream if stream owner - inStream.Dispose(); - } - } - } - } + try + { + using var gzipOutput = new GZipOutputStream(outStream, bufferSize); + gzipOutput.SetLevel(level); + gzipOutput.IsStreamOwner = isStreamOwner; + Core.StreamUtils.Copy(inStream, gzipOutput, new byte[bufferSize]); + } + finally + { + if (isStreamOwner) + { + // outStream is closed by the GZipOutputStream if stream owner + inStream.Dispose(); + } + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs index 00c1b5496..3d4130833 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs @@ -1,54 +1,53 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib.GZip +namespace MelonLoader.ICSharpCode.SharpZipLib.GZip; + +/// +/// GZipException represents exceptions specific to GZip classes and code. +/// +[Serializable] +public class GZipException : SharpZipBaseException { - /// - /// GZipException represents exceptions specific to GZip classes and code. - /// - [Serializable] - public class GZipException : SharpZipBaseException - { - /// - /// Initialise a new instance of . - /// - public GZipException() - { - } + /// + /// Initialise a new instance of . + /// + public GZipException() + { + } - /// - /// Initialise a new instance of with its message string. - /// - /// A that describes the error. - public GZipException(string message) - : base(message) - { - } + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public GZipException(string message) + : base(message) + { + } - /// - /// Initialise a new instance of . - /// - /// A that describes the error. - /// The that caused this exception. - public GZipException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public GZipException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the GZipException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected GZipException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the GZipException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected GZipException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs index 7382fb6a7..50232fd45 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs @@ -3,359 +3,357 @@ using MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; using System; using System.IO; -using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.GZip +namespace MelonLoader.ICSharpCode.SharpZipLib.GZip; + +/// +/// This filter stream is used to decompress a "GZIP" format stream. +/// The "GZIP" format is described baseInputStream RFC 1952. +/// +/// author of the original java version : John Leuner +/// +/// This sample shows how to unzip a gzipped file +/// +/// using System; +/// using System.IO; +/// +/// using MelonLoader.ICSharpCode.SharpZipLib.Core; +/// using MelonLoader.ICSharpCode.SharpZipLib.GZip; +/// +/// class MainClass +/// { +/// public static void Main(string[] args) +/// { +/// using (Stream inStream = new GZipInputStream(File.OpenRead(args[0]))) +/// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { +/// byte[] buffer = new byte[4096]; +/// StreamUtils.Copy(inStream, outStream, buffer); +/// } +/// } +/// } +/// +/// +public class GZipInputStream : InflaterInputStream { - /// - /// This filter stream is used to decompress a "GZIP" format stream. - /// The "GZIP" format is described baseInputStream RFC 1952. - /// - /// author of the original java version : John Leuner - /// - /// This sample shows how to unzip a gzipped file - /// - /// using System; - /// using System.IO; - /// - /// using MelonLoader.ICSharpCode.SharpZipLib.Core; - /// using MelonLoader.ICSharpCode.SharpZipLib.GZip; - /// - /// class MainClass - /// { - /// public static void Main(string[] args) - /// { - /// using (Stream inStream = new GZipInputStream(File.OpenRead(args[0]))) - /// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { - /// byte[] buffer = new byte[4096]; - /// StreamUtils.Copy(inStream, outStream, buffer); - /// } - /// } - /// } - /// - /// - public class GZipInputStream : InflaterInputStream - { - #region Instance Fields - - /// - /// CRC-32 value for uncompressed data - /// - protected Crc32 crc; - - /// - /// Flag to indicate if we've read the GZIP header yet for the current member (block of compressed data). - /// This is tracked per-block as the file is parsed. - /// - private bool readGZIPHeader; - - /// - /// Flag to indicate if at least one block in a stream with concatenated blocks was read successfully. - /// This allows us to exit gracefully if downstream data is not in gzip format. - /// - private bool completedLastBlock; - - private string fileName; - - #endregion Instance Fields - - #region Constructors - - /// - /// Creates a GZipInputStream with the default buffer size - /// - /// - /// The stream to read compressed data from (baseInputStream GZIP format) - /// - public GZipInputStream(Stream baseInputStream) - : this(baseInputStream, 4096) - { - } - - /// - /// Creates a GZIPInputStream with the specified buffer size - /// - /// - /// The stream to read compressed data from (baseInputStream GZIP format) - /// - /// - /// Size of the buffer to use - /// - public GZipInputStream(Stream baseInputStream, int size) - : base(baseInputStream, new Inflater(true), size) - { - } - - #endregion Constructors - - #region Stream overrides - - /// - /// Reads uncompressed data into an array of bytes - /// - /// - /// The buffer to read uncompressed data into - /// - /// - /// The offset indicating where the data should be placed - /// - /// - /// The number of uncompressed bytes to be read - /// - /// Returns the number of bytes actually read. - public override int Read(byte[] buffer, int offset, int count) - { - // A GZIP file can contain multiple blocks of compressed data, although this is quite rare. - // A compressed block could potentially be empty, so we need to loop until we reach EOF or - // we find data. - while (true) - { - // If we haven't read the header for this block, read it - if (!readGZIPHeader) - { - // Try to read header. If there is no header (0 bytes available), this is EOF. If there is - // an incomplete header, this will throw an exception. - try - { - if (!ReadHeader()) - { - return 0; - } - } - catch (Exception ex) when (completedLastBlock && (ex is GZipException || ex is EndOfStreamException)) - { - // if we completed the last block (i.e. we're in a stream that has multiple blocks concatenated - // we want to return gracefully from any header parsing exceptions since sometimes there may - // be trailing garbage on a stream - return 0; - } - } - - // Try to read compressed data - int bytesRead = base.Read(buffer, offset, count); - if (bytesRead > 0) - { - crc.Update(new ArraySegment(buffer, offset, bytesRead)); - } - - // If this is the end of stream, read the footer - if (inf.IsFinished) - { - ReadFooter(); - } - - // Attempting to read 0 bytes will never yield any bytesRead, so we return instead of looping forever - if (bytesRead > 0 || count == 0) - { - return bytesRead; - } - } - } - - /// - /// Retrieves the filename header field for the block last read - /// - /// - public string GetFilename() - { - return fileName; - } - - #endregion Stream overrides - - #region Support routines - - private bool ReadHeader() - { - // Initialize CRC for this block - crc = new Crc32(); - - // Make sure there is data in file. We can't rely on ReadLeByte() to fill the buffer, as this could be EOF, - // which is fine, but ReadLeByte() throws an exception if it doesn't find data, so we do this part ourselves. - if (inputBuffer.Available <= 0) - { - inputBuffer.Fill(); - if (inputBuffer.Available <= 0) - { - // No header, EOF. - return false; - } - } - - var headCRC = new Crc32(); - - // 1. Check the two magic bytes - - var magic = inputBuffer.ReadLeByte(); - headCRC.Update(magic); - if (magic != GZipConstants.ID1) - { - throw new GZipException("Error GZIP header, first magic byte doesn't match"); - } - - magic = inputBuffer.ReadLeByte(); - if (magic != GZipConstants.ID2) - { - throw new GZipException("Error GZIP header, second magic byte doesn't match"); - } - headCRC.Update(magic); - - // 2. Check the compression type (must be 8) - var compressionType = inputBuffer.ReadLeByte(); - - if (compressionType != GZipConstants.CompressionMethodDeflate) - { - throw new GZipException("Error GZIP header, data not in deflate format"); - } - headCRC.Update(compressionType); - - // 3. Check the flags - var flagsByte = inputBuffer.ReadLeByte(); - - headCRC.Update(flagsByte); - - // 3.1 Check the reserved bits are zero - - if ((flagsByte & 0xE0) != 0) - { - throw new GZipException("Reserved flag bits in GZIP header != 0"); - } - - var flags = (GZipFlags)flagsByte; - - // 4.-6. Skip the modification time, extra flags, and OS type - for (int i = 0; i < 6; i++) - { - headCRC.Update(inputBuffer.ReadLeByte()); - } - - // 7. Read extra field - if (flags.HasFlag(GZipFlags.FEXTRA)) - { - // XLEN is total length of extra subfields, we will skip them all - var len1 = inputBuffer.ReadLeByte(); - var len2 = inputBuffer.ReadLeByte(); - - headCRC.Update(len1); - headCRC.Update(len2); - - int extraLen = (len2 << 8) | len1; // gzip is LSB first - for (int i = 0; i < extraLen; i++) - { - headCRC.Update(inputBuffer.ReadLeByte()); - } - } - - // 8. Read file name - if (flags.HasFlag(GZipFlags.FNAME)) - { - var fname = new byte[1024]; - var fnamePos = 0; - int readByte; - while ((readByte = inputBuffer.ReadLeByte()) > 0) - { - if (fnamePos < 1024) - { - fname[fnamePos++] = (byte)readByte; - } - headCRC.Update(readByte); - } - - headCRC.Update(readByte); - - fileName = GZipConstants.Encoding.GetString(fname, 0, fnamePos); - } - else - { - fileName = null; - } - - // 9. Read comment - if (flags.HasFlag(GZipFlags.FCOMMENT)) - { - int readByte; - while ((readByte = inputBuffer.ReadLeByte()) > 0) - { - headCRC.Update(readByte); - } - - headCRC.Update(readByte); - } - - // 10. Read header CRC - if (flags.HasFlag(GZipFlags.FHCRC)) - { - int tempByte; - int crcval = inputBuffer.ReadLeByte(); - if (crcval < 0) - { - throw new EndOfStreamException("EOS reading GZIP header"); - } - - tempByte = inputBuffer.ReadLeByte(); - if (tempByte < 0) - { - throw new EndOfStreamException("EOS reading GZIP header"); - } - - crcval = (crcval << 8) | tempByte; - if (crcval != ((int)headCRC.Value & 0xffff)) - { - throw new GZipException("Header CRC value mismatch"); - } - } - - readGZIPHeader = true; - return true; - } - - private void ReadFooter() - { - byte[] footer = new byte[8]; - - // End of stream; reclaim all bytes from inf, read the final byte count, and reset the inflator - long bytesRead = inf.TotalOut & 0xffffffff; - inputBuffer.Available += inf.RemainingInput; - inf.Reset(); - - // Read footer from inputBuffer - int needed = 8; - while (needed > 0) - { - int count = inputBuffer.ReadClearTextBuffer(footer, 8 - needed, needed); - if (count <= 0) - { - throw new EndOfStreamException("EOS reading GZIP footer"); - } - needed -= count; // Jewel Jan 16 - } - - // Calculate CRC - int crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8) | ((footer[2] & 0xff) << 16) | (footer[3] << 24); - if (crcval != (int)crc.Value) - { - throw new GZipException("GZIP crc sum mismatch, theirs \"" + crcval + "\" and ours \"" + (int)crc.Value); - } - - // NOTE The total here is the original total modulo 2 ^ 32. - uint total = - (uint)((uint)footer[4] & 0xff) | - (uint)(((uint)footer[5] & 0xff) << 8) | - (uint)(((uint)footer[6] & 0xff) << 16) | - (uint)((uint)footer[7] << 24); - - if (bytesRead != total) - { - throw new GZipException("Number of bytes mismatch in footer"); - } - - // Mark header read as false so if another header exists, we'll continue reading through the file - readGZIPHeader = false; - - // Indicate that we succeeded on at least one block so we can exit gracefully if there is trailing garbage downstream - completedLastBlock = true; - } - - #endregion Support routines - } + #region Instance Fields + + /// + /// CRC-32 value for uncompressed data + /// + protected Crc32 crc; + + /// + /// Flag to indicate if we've read the GZIP header yet for the current member (block of compressed data). + /// This is tracked per-block as the file is parsed. + /// + private bool readGZIPHeader; + + /// + /// Flag to indicate if at least one block in a stream with concatenated blocks was read successfully. + /// This allows us to exit gracefully if downstream data is not in gzip format. + /// + private bool completedLastBlock; + + private string fileName; + + #endregion Instance Fields + + #region Constructors + + /// + /// Creates a GZipInputStream with the default buffer size + /// + /// + /// The stream to read compressed data from (baseInputStream GZIP format) + /// + public GZipInputStream(Stream baseInputStream) + : this(baseInputStream, 4096) + { + } + + /// + /// Creates a GZIPInputStream with the specified buffer size + /// + /// + /// The stream to read compressed data from (baseInputStream GZIP format) + /// + /// + /// Size of the buffer to use + /// + public GZipInputStream(Stream baseInputStream, int size) + : base(baseInputStream, new Inflater(true), size) + { + } + + #endregion Constructors + + #region Stream overrides + + /// + /// Reads uncompressed data into an array of bytes + /// + /// + /// The buffer to read uncompressed data into + /// + /// + /// The offset indicating where the data should be placed + /// + /// + /// The number of uncompressed bytes to be read + /// + /// Returns the number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + // A GZIP file can contain multiple blocks of compressed data, although this is quite rare. + // A compressed block could potentially be empty, so we need to loop until we reach EOF or + // we find data. + while (true) + { + // If we haven't read the header for this block, read it + if (!readGZIPHeader) + { + // Try to read header. If there is no header (0 bytes available), this is EOF. If there is + // an incomplete header, this will throw an exception. + try + { + if (!ReadHeader()) + { + return 0; + } + } + catch (Exception ex) when (completedLastBlock && (ex is GZipException || ex is EndOfStreamException)) + { + // if we completed the last block (i.e. we're in a stream that has multiple blocks concatenated + // we want to return gracefully from any header parsing exceptions since sometimes there may + // be trailing garbage on a stream + return 0; + } + } + + // Try to read compressed data + var bytesRead = base.Read(buffer, offset, count); + if (bytesRead > 0) + { + crc.Update(new ArraySegment(buffer, offset, bytesRead)); + } + + // If this is the end of stream, read the footer + if (inf.IsFinished) + { + ReadFooter(); + } + + // Attempting to read 0 bytes will never yield any bytesRead, so we return instead of looping forever + if (bytesRead > 0 || count == 0) + { + return bytesRead; + } + } + } + + /// + /// Retrieves the filename header field for the block last read + /// + /// + public string GetFilename() + { + return fileName; + } + + #endregion Stream overrides + + #region Support routines + + private bool ReadHeader() + { + // Initialize CRC for this block + crc = new Crc32(); + + // Make sure there is data in file. We can't rely on ReadLeByte() to fill the buffer, as this could be EOF, + // which is fine, but ReadLeByte() throws an exception if it doesn't find data, so we do this part ourselves. + if (inputBuffer.Available <= 0) + { + inputBuffer.Fill(); + if (inputBuffer.Available <= 0) + { + // No header, EOF. + return false; + } + } + + var headCRC = new Crc32(); + + // 1. Check the two magic bytes + + var magic = inputBuffer.ReadLeByte(); + headCRC.Update(magic); + if (magic != GZipConstants.ID1) + { + throw new GZipException("Error GZIP header, first magic byte doesn't match"); + } + + magic = inputBuffer.ReadLeByte(); + if (magic != GZipConstants.ID2) + { + throw new GZipException("Error GZIP header, second magic byte doesn't match"); + } + headCRC.Update(magic); + + // 2. Check the compression type (must be 8) + var compressionType = inputBuffer.ReadLeByte(); + + if (compressionType != GZipConstants.CompressionMethodDeflate) + { + throw new GZipException("Error GZIP header, data not in deflate format"); + } + headCRC.Update(compressionType); + + // 3. Check the flags + var flagsByte = inputBuffer.ReadLeByte(); + + headCRC.Update(flagsByte); + + // 3.1 Check the reserved bits are zero + + if ((flagsByte & 0xE0) != 0) + { + throw new GZipException("Reserved flag bits in GZIP header != 0"); + } + + var flags = (GZipFlags)flagsByte; + + // 4.-6. Skip the modification time, extra flags, and OS type + for (var i = 0; i < 6; i++) + { + headCRC.Update(inputBuffer.ReadLeByte()); + } + + // 7. Read extra field + if (flags.HasFlag(GZipFlags.FEXTRA)) + { + // XLEN is total length of extra subfields, we will skip them all + var len1 = inputBuffer.ReadLeByte(); + var len2 = inputBuffer.ReadLeByte(); + + headCRC.Update(len1); + headCRC.Update(len2); + + var extraLen = (len2 << 8) | len1; // gzip is LSB first + for (var i = 0; i < extraLen; i++) + { + headCRC.Update(inputBuffer.ReadLeByte()); + } + } + + // 8. Read file name + if (flags.HasFlag(GZipFlags.FNAME)) + { + var fname = new byte[1024]; + var fnamePos = 0; + int readByte; + while ((readByte = inputBuffer.ReadLeByte()) > 0) + { + if (fnamePos < 1024) + { + fname[fnamePos++] = (byte)readByte; + } + headCRC.Update(readByte); + } + + headCRC.Update(readByte); + + fileName = GZipConstants.Encoding.GetString(fname, 0, fnamePos); + } + else + { + fileName = null; + } + + // 9. Read comment + if (flags.HasFlag(GZipFlags.FCOMMENT)) + { + int readByte; + while ((readByte = inputBuffer.ReadLeByte()) > 0) + { + headCRC.Update(readByte); + } + + headCRC.Update(readByte); + } + + // 10. Read header CRC + if (flags.HasFlag(GZipFlags.FHCRC)) + { + int tempByte; + int crcval = inputBuffer.ReadLeByte(); + if (crcval < 0) + { + throw new EndOfStreamException("EOS reading GZIP header"); + } + + tempByte = inputBuffer.ReadLeByte(); + if (tempByte < 0) + { + throw new EndOfStreamException("EOS reading GZIP header"); + } + + crcval = (crcval << 8) | tempByte; + if (crcval != ((int)headCRC.Value & 0xffff)) + { + throw new GZipException("Header CRC value mismatch"); + } + } + + readGZIPHeader = true; + return true; + } + + private void ReadFooter() + { + var footer = new byte[8]; + + // End of stream; reclaim all bytes from inf, read the final byte count, and reset the inflator + var bytesRead = inf.TotalOut & 0xffffffff; + inputBuffer.Available += inf.RemainingInput; + inf.Reset(); + + // Read footer from inputBuffer + var needed = 8; + while (needed > 0) + { + var count = inputBuffer.ReadClearTextBuffer(footer, 8 - needed, needed); + if (count <= 0) + { + throw new EndOfStreamException("EOS reading GZIP footer"); + } + needed -= count; // Jewel Jan 16 + } + + // Calculate CRC + var crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8) | ((footer[2] & 0xff) << 16) | (footer[3] << 24); + if (crcval != (int)crc.Value) + { + throw new GZipException("GZIP crc sum mismatch, theirs \"" + crcval + "\" and ours \"" + (int)crc.Value); + } + + // NOTE The total here is the original total modulo 2 ^ 32. + var total = + (uint)footer[4] & 0xff | + ((uint)footer[5] & 0xff) << 8 | + ((uint)footer[6] & 0xff) << 16 | + (uint)footer[7] << 24; + + if (bytesRead != total) + { + throw new GZipException("Number of bytes mismatch in footer"); + } + + // Mark header read as false so if another header exists, we'll continue reading through the file + readGZIPHeader = false; + + // Indicate that we succeeded on at least one block so we can exit gracefully if there is trailing garbage downstream + completedLastBlock = true; + } + + #endregion Support routines } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs index 609599bd2..d06cf3465 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs @@ -3,260 +3,259 @@ using MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; using System; using System.IO; -using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.GZip +namespace MelonLoader.ICSharpCode.SharpZipLib.GZip; + +/// +/// This filter stream is used to compress a stream into a "GZIP" stream. +/// The "GZIP" format is described in RFC 1952. +/// +/// author of the original java version : John Leuner +/// +/// This sample shows how to gzip a file +/// +/// using System; +/// using System.IO; +/// +/// using MelonLoader.ICSharpCode.SharpZipLib.GZip; +/// using MelonLoader.ICSharpCode.SharpZipLib.Core; +/// +/// class MainClass +/// { +/// public static void Main(string[] args) +/// { +/// using (Stream s = new GZipOutputStream(File.Create(args[0] + ".gz"))) +/// using (FileStream fs = File.OpenRead(args[0])) { +/// byte[] writeData = new byte[4096]; +/// Streamutils.Copy(s, fs, writeData); +/// } +/// } +/// } +/// } +/// +/// +public class GZipOutputStream : DeflaterOutputStream { - /// - /// This filter stream is used to compress a stream into a "GZIP" stream. - /// The "GZIP" format is described in RFC 1952. - /// - /// author of the original java version : John Leuner - /// - /// This sample shows how to gzip a file - /// - /// using System; - /// using System.IO; - /// - /// using MelonLoader.ICSharpCode.SharpZipLib.GZip; - /// using MelonLoader.ICSharpCode.SharpZipLib.Core; - /// - /// class MainClass - /// { - /// public static void Main(string[] args) - /// { - /// using (Stream s = new GZipOutputStream(File.Create(args[0] + ".gz"))) - /// using (FileStream fs = File.OpenRead(args[0])) { - /// byte[] writeData = new byte[4096]; - /// Streamutils.Copy(s, fs, writeData); - /// } - /// } - /// } - /// } - /// - /// - public class GZipOutputStream : DeflaterOutputStream - { - private enum OutputState - { - Header, - Footer, - Finished, - Closed, - }; - - #region Instance Fields - - /// - /// CRC-32 value for uncompressed data - /// - protected Crc32 crc = new Crc32(); - - private OutputState state_ = OutputState.Header; - - private string fileName; - - private GZipFlags flags = 0; - - #endregion Instance Fields - - #region Constructors - - /// - /// Creates a GzipOutputStream with the default buffer size - /// - /// - /// The stream to read data (to be compressed) from - /// - public GZipOutputStream(Stream baseOutputStream) - : this(baseOutputStream, 4096) - { - } - - /// - /// Creates a GZipOutputStream with the specified buffer size - /// - /// - /// The stream to read data (to be compressed) from - /// - /// - /// Size of the buffer to use - /// - public GZipOutputStream(Stream baseOutputStream, int size) : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size) - { - } - - #endregion Constructors - - #region Public API - - /// - /// Sets the active compression level (0-9). The new level will be activated - /// immediately. - /// - /// The compression level to set. - /// - /// Level specified is not supported. - /// - /// - public void SetLevel(int level) - { - if (level < Deflater.NO_COMPRESSION || level > Deflater.BEST_COMPRESSION) - throw new ArgumentOutOfRangeException(nameof(level), "Compression level must be 0-9"); - - deflater_.SetLevel(level); - } - - /// - /// Get the current compression level. - /// - /// The current compression level. - public int GetLevel() - { - return deflater_.GetLevel(); - } - - /// - /// Original filename - /// - public string FileName - { - get => fileName; - set - { - fileName = CleanFilename(value); - if (string.IsNullOrEmpty(fileName)) - { - flags &= ~GZipFlags.FNAME; - } - else - { - flags |= GZipFlags.FNAME; - } - } - } - - #endregion Public API - - #region Stream overrides - - /// - /// Write given buffer to output updating crc - /// - /// Buffer to write - /// Offset of first byte in buf to write - /// Number of bytes to write - public override void Write(byte[] buffer, int offset, int count) - { - if (state_ == OutputState.Header) - { - WriteHeader(); - } - - if (state_ != OutputState.Footer) - { - throw new InvalidOperationException("Write not permitted in current state"); - } - - crc.Update(new ArraySegment(buffer, offset, count)); - base.Write(buffer, offset, count); - } - - /// - /// Writes remaining compressed output data to the output stream - /// and closes it. - /// - protected override void Dispose(bool disposing) - { - try - { - Finish(); - } - finally - { - if (state_ != OutputState.Closed) - { - state_ = OutputState.Closed; - if (IsStreamOwner) - { - baseOutputStream_.Dispose(); - } - } - } - } - - /// - /// Flushes the stream by ensuring the header is written, and then calling Flush - /// on the deflater. - /// - public override void Flush() - { - if (state_ == OutputState.Header) - { - WriteHeader(); - } - - base.Flush(); - } - - #endregion Stream overrides - - #region DeflaterOutputStream overrides - - /// - /// Finish compression and write any footer information required to stream - /// - public override void Finish() - { - // If no data has been written a header should be added. - if (state_ == OutputState.Header) - { - WriteHeader(); - } - - if (state_ == OutputState.Footer) - { - state_ = OutputState.Finished; - base.Finish(); - - var totalin = (uint)(deflater_.TotalIn & 0xffffffff); - var crcval = (uint)(crc.Value & 0xffffffff); - - byte[] gzipFooter; - - unchecked - { - gzipFooter = new byte[] { - (byte) crcval, (byte) (crcval >> 8), - (byte) (crcval >> 16), (byte) (crcval >> 24), - - (byte) totalin, (byte) (totalin >> 8), - (byte) (totalin >> 16), (byte) (totalin >> 24) - }; - } - - baseOutputStream_.Write(gzipFooter, 0, gzipFooter.Length); - } - } - - #endregion DeflaterOutputStream overrides - - #region Support Routines - - private static string CleanFilename(string path) - => path.Substring(path.LastIndexOf('/') + 1); - - private void WriteHeader() - { - if (state_ == OutputState.Header) - { - state_ = OutputState.Footer; - - var mod_time = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals - byte[] gzipHeader = { + private enum OutputState + { + Header, + Footer, + Finished, + Closed, + }; + + #region Instance Fields + + /// + /// CRC-32 value for uncompressed data + /// + protected Crc32 crc = new(); + + private OutputState state_ = OutputState.Header; + + private string fileName; + + private GZipFlags flags = 0; + + #endregion Instance Fields + + #region Constructors + + /// + /// Creates a GzipOutputStream with the default buffer size + /// + /// + /// The stream to read data (to be compressed) from + /// + public GZipOutputStream(Stream baseOutputStream) + : this(baseOutputStream, 4096) + { + } + + /// + /// Creates a GZipOutputStream with the specified buffer size + /// + /// + /// The stream to read data (to be compressed) from + /// + /// + /// Size of the buffer to use + /// + public GZipOutputStream(Stream baseOutputStream, int size) : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size) + { + } + + #endregion Constructors + + #region Public API + + /// + /// Sets the active compression level (0-9). The new level will be activated + /// immediately. + /// + /// The compression level to set. + /// + /// Level specified is not supported. + /// + /// + public void SetLevel(int level) + { + if (level is < Deflater.NO_COMPRESSION or > Deflater.BEST_COMPRESSION) + throw new ArgumentOutOfRangeException(nameof(level), "Compression level must be 0-9"); + + deflater_.SetLevel(level); + } + + /// + /// Get the current compression level. + /// + /// The current compression level. + public int GetLevel() + { + return deflater_.GetLevel(); + } + + /// + /// Original filename + /// + public string FileName + { + get => fileName; + set + { + fileName = CleanFilename(value); + if (string.IsNullOrEmpty(fileName)) + { + flags &= ~GZipFlags.FNAME; + } + else + { + flags |= GZipFlags.FNAME; + } + } + } + + #endregion Public API + + #region Stream overrides + + /// + /// Write given buffer to output updating crc + /// + /// Buffer to write + /// Offset of first byte in buf to write + /// Number of bytes to write + public override void Write(byte[] buffer, int offset, int count) + { + if (state_ == OutputState.Header) + { + WriteHeader(); + } + + if (state_ != OutputState.Footer) + { + throw new InvalidOperationException("Write not permitted in current state"); + } + + crc.Update(new ArraySegment(buffer, offset, count)); + base.Write(buffer, offset, count); + } + + /// + /// Writes remaining compressed output data to the output stream + /// and closes it. + /// + protected override void Dispose(bool disposing) + { + try + { + Finish(); + } + finally + { + if (state_ != OutputState.Closed) + { + state_ = OutputState.Closed; + if (IsStreamOwner) + { + baseOutputStream_.Dispose(); + } + } + } + } + + /// + /// Flushes the stream by ensuring the header is written, and then calling Flush + /// on the deflater. + /// + public override void Flush() + { + if (state_ == OutputState.Header) + { + WriteHeader(); + } + + base.Flush(); + } + + #endregion Stream overrides + + #region DeflaterOutputStream overrides + + /// + /// Finish compression and write any footer information required to stream + /// + public override void Finish() + { + // If no data has been written a header should be added. + if (state_ == OutputState.Header) + { + WriteHeader(); + } + + if (state_ == OutputState.Footer) + { + state_ = OutputState.Finished; + base.Finish(); + + var totalin = (uint)(deflater_.TotalIn & 0xffffffff); + var crcval = (uint)(crc.Value & 0xffffffff); + + byte[] gzipFooter; + + unchecked + { + gzipFooter = new byte[] { + (byte) crcval, (byte) (crcval >> 8), + (byte) (crcval >> 16), (byte) (crcval >> 24), + + (byte) totalin, (byte) (totalin >> 8), + (byte) (totalin >> 16), (byte) (totalin >> 24) + }; + } + + baseOutputStream_.Write(gzipFooter, 0, gzipFooter.Length); + } + } + + #endregion DeflaterOutputStream overrides + + #region Support Routines + + private static string CleanFilename(string path) + => path[(path.LastIndexOf('/') + 1)..]; + + private void WriteHeader() + { + if (state_ == OutputState.Header) + { + state_ = OutputState.Footer; + + var mod_time = (int)((DateTime.Now.Ticks - new DateTime(1970, 1, 1).Ticks) / 10000000L); // Ticks give back 100ns intervals + byte[] gzipHeader = { // The two magic bytes - GZipConstants.ID1, - GZipConstants.ID2, + GZipConstants.ID1, + GZipConstants.ID2, // The compression type GZipConstants.CompressionMethodDeflate, @@ -266,28 +265,27 @@ private void WriteHeader() // The modification time (byte) mod_time, (byte) (mod_time >> 8), - (byte) (mod_time >> 16), (byte) (mod_time >> 24), + (byte) (mod_time >> 16), (byte) (mod_time >> 24), // The extra flags 0, // The OS type (unknown) 255 - }; + }; - baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length); + baseOutputStream_.Write(gzipHeader, 0, gzipHeader.Length); - if (flags.HasFlag(GZipFlags.FNAME)) - { - var fname = GZipConstants.Encoding.GetBytes(fileName); - baseOutputStream_.Write(fname, 0, fname.Length); + if (flags.HasFlag(GZipFlags.FNAME)) + { + var fname = GZipConstants.Encoding.GetBytes(fileName); + baseOutputStream_.Write(fname, 0, fname.Length); - // End filename string with a \0 - baseOutputStream_.Write(new byte[] { 0 }, 0, 1); - } - } - } + // End filename string with a \0 + baseOutputStream_.Write(new byte[] { 0 }, 0, 1); + } + } + } - #endregion Support Routines - } + #endregion Support Routines } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs index 2c3eb3ca9..81c740b9d 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs @@ -1,63 +1,62 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw +namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw; + +/// +/// This class contains constants used for LZW +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] +public sealed class LzwConstants { - /// - /// This class contains constants used for LZW - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] - sealed public class LzwConstants - { - /// - /// Magic number found at start of LZW header: 0x1f 0x9d - /// - public const int MAGIC = 0x1f9d; - - /// - /// Maximum number of bits per code - /// - public const int MAX_BITS = 16; - - /* 3rd header byte: - * bit 0..4 Number of compression bits - * bit 5 Extended header - * bit 6 Free - * bit 7 Block mode - */ - - /// - /// Mask for 'number of compression bits' - /// - public const int BIT_MASK = 0x1f; - - /// - /// Indicates the presence of a fourth header byte - /// - public const int EXTENDED_MASK = 0x20; - - //public const int FREE_MASK = 0x40; - - /// - /// Reserved bits - /// - public const int RESERVED_MASK = 0x60; - - /// - /// Block compression: if table is full and compression rate is dropping, - /// clear the dictionary. - /// - public const int BLOCK_MODE_MASK = 0x80; - - /// - /// LZW file header size (in bytes) - /// - public const int HDR_SIZE = 3; - - /// - /// Initial number of bits per code - /// - public const int INIT_BITS = 9; - - private LzwConstants() - { - } - } + /// + /// Magic number found at start of LZW header: 0x1f 0x9d + /// + public const int MAGIC = 0x1f9d; + + /// + /// Maximum number of bits per code + /// + public const int MAX_BITS = 16; + + /* 3rd header byte: + * bit 0..4 Number of compression bits + * bit 5 Extended header + * bit 6 Free + * bit 7 Block mode + */ + + /// + /// Mask for 'number of compression bits' + /// + public const int BIT_MASK = 0x1f; + + /// + /// Indicates the presence of a fourth header byte + /// + public const int EXTENDED_MASK = 0x20; + + //public const int FREE_MASK = 0x40; + + /// + /// Reserved bits + /// + public const int RESERVED_MASK = 0x60; + + /// + /// Block compression: if table is full and compression rate is dropping, + /// clear the dictionary. + /// + public const int BLOCK_MODE_MASK = 0x80; + + /// + /// LZW file header size (in bytes) + /// + public const int HDR_SIZE = 3; + + /// + /// Initial number of bits per code + /// + public const int INIT_BITS = 9; + + private LzwConstants() + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs index 77e166148..434c31be9 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs @@ -1,54 +1,53 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw +namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw; + +/// +/// LzwException represents exceptions specific to LZW classes and code. +/// +[Serializable] +public class LzwException : SharpZipBaseException { - /// - /// LzwException represents exceptions specific to LZW classes and code. - /// - [Serializable] - public class LzwException : SharpZipBaseException - { - /// - /// Initialise a new instance of . - /// - public LzwException() - { - } + /// + /// Initialise a new instance of . + /// + public LzwException() + { + } - /// - /// Initialise a new instance of with its message string. - /// - /// A that describes the error. - public LzwException(string message) - : base(message) - { - } + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public LzwException(string message) + : base(message) + { + } - /// - /// Initialise a new instance of . - /// - /// A that describes the error. - /// The that caused this exception. - public LzwException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public LzwException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the LzwException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected LzwException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the LzwException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected LzwException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs index 12f7f11aa..2801ce7f5 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs @@ -1,572 +1,569 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw +namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw; + +/// +/// This filter stream is used to decompress a LZW format stream. +/// Specifically, a stream that uses the LZC compression method. +/// This file format is usually associated with the .Z file extension. +/// +/// See http://en.wikipedia.org/wiki/Compress +/// See http://wiki.wxwidgets.org/Development:_Z_File_Format +/// +/// The file header consists of 3 (or optionally 4) bytes. The first two bytes +/// contain the magic marker "0x1f 0x9d", followed by a byte of flags. +/// +/// Based on Java code by Ronald Tschalar, which in turn was based on the unlzw.c +/// code in the gzip package. +/// +/// This sample shows how to unzip a compressed file +/// +/// using System; +/// using System.IO; +/// +/// using MelonLoader.ICSharpCode.SharpZipLib.Core; +/// using MelonLoader.ICSharpCode.SharpZipLib.LZW; +/// +/// class MainClass +/// { +/// public static void Main(string[] args) +/// { +/// using (Stream inStream = new LzwInputStream(File.OpenRead(args[0]))) +/// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { +/// byte[] buffer = new byte[4096]; +/// StreamUtils.Copy(inStream, outStream, buffer); +/// // OR +/// inStream.Read(buffer, 0, buffer.Length); +/// // now do something with the buffer +/// } +/// } +/// } +/// +/// +public class LzwInputStream : Stream { - /// - /// This filter stream is used to decompress a LZW format stream. - /// Specifically, a stream that uses the LZC compression method. - /// This file format is usually associated with the .Z file extension. - /// - /// See http://en.wikipedia.org/wiki/Compress - /// See http://wiki.wxwidgets.org/Development:_Z_File_Format - /// - /// The file header consists of 3 (or optionally 4) bytes. The first two bytes - /// contain the magic marker "0x1f 0x9d", followed by a byte of flags. - /// - /// Based on Java code by Ronald Tschalar, which in turn was based on the unlzw.c - /// code in the gzip package. - /// - /// This sample shows how to unzip a compressed file - /// - /// using System; - /// using System.IO; - /// - /// using MelonLoader.ICSharpCode.SharpZipLib.Core; - /// using MelonLoader.ICSharpCode.SharpZipLib.LZW; - /// - /// class MainClass - /// { - /// public static void Main(string[] args) - /// { - /// using (Stream inStream = new LzwInputStream(File.OpenRead(args[0]))) - /// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { - /// byte[] buffer = new byte[4096]; - /// StreamUtils.Copy(inStream, outStream, buffer); - /// // OR - /// inStream.Read(buffer, 0, buffer.Length); - /// // now do something with the buffer - /// } - /// } - /// } - /// - /// - public class LzwInputStream : Stream - { - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner { get; set; } = true; - - /// - /// Creates a LzwInputStream - /// - /// - /// The stream to read compressed data from (baseInputStream LZW format) - /// - public LzwInputStream(Stream baseInputStream) - { - this.baseInputStream = baseInputStream; - } - - /// - /// See - /// - /// - public override int ReadByte() - { - int b = Read(one, 0, 1); - if (b == 1) - return (one[0] & 0xff); - return -1; - } - - /// - /// Reads decompressed data into the provided buffer byte array - /// - /// - /// The array to read and decompress data into - /// - /// - /// The offset indicating where the data should be placed - /// - /// - /// The number of bytes to decompress - /// - /// The number of bytes read. Zero signals the end of stream - public override int Read(byte[] buffer, int offset, int count) - { - if (!headerParsed) - ParseHeader(); - - if (eof) - return 0; - - int start = offset; - - /* Using local copies of various variables speeds things up by as - * much as 30% in Java! Performance not tested in C#. - */ - int[] lTabPrefix = tabPrefix; - byte[] lTabSuffix = tabSuffix; - byte[] lStack = stack; - int lNBits = nBits; - int lMaxCode = maxCode; - int lMaxMaxCode = maxMaxCode; - int lBitMask = bitMask; - int lOldCode = oldCode; - byte lFinChar = finChar; - int lStackP = stackP; - int lFreeEnt = freeEnt; - byte[] lData = data; - int lBitPos = bitPos; - - // empty stack if stuff still left - int sSize = lStack.Length - lStackP; - if (sSize > 0) - { - int num = (sSize >= count) ? count : sSize; - Array.Copy(lStack, lStackP, buffer, offset, num); - offset += num; - count -= num; - lStackP += num; - } - - if (count == 0) - { - stackP = lStackP; - return offset - start; - } - - // loop, filling local buffer until enough data has been decompressed - MainLoop: - do - { - if (end < EXTRA) - { - Fill(); - } - - int bitIn = (got > 0) ? (end - end % lNBits) << 3 : - (end << 3) - (lNBits - 1); - - while (lBitPos < bitIn) - { - #region A - - // handle 1-byte reads correctly - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - maxMaxCode = lMaxMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - return offset - start; - } - - // check for code-width expansion - if (lFreeEnt > lMaxCode) - { - int nBytes = lNBits << 3; - lBitPos = (lBitPos - 1) + - nBytes - (lBitPos - 1 + nBytes) % nBytes; - - lNBits++; - lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : - (1 << lNBits) - 1; - - lBitMask = (1 << lNBits) - 1; - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } - - #endregion A - - #region B - - // read next code - int pos = lBitPos >> 3; - int code = (((lData[pos] & 0xFF) | - ((lData[pos + 1] & 0xFF) << 8) | - ((lData[pos + 2] & 0xFF) << 16)) >> - (lBitPos & 0x7)) & lBitMask; - - lBitPos += lNBits; - - // handle first iteration - if (lOldCode == -1) - { - if (code >= 256) - throw new LzwException("corrupt input: " + code + " > 255"); - - lFinChar = (byte)(lOldCode = code); - buffer[offset++] = lFinChar; - count--; - continue; - } - - // handle CLEAR code - if (code == TBL_CLEAR && blockMode) - { - Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); - lFreeEnt = TBL_FIRST - 1; - - int nBytes = lNBits << 3; - lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; - lNBits = LzwConstants.INIT_BITS; - lMaxCode = (1 << lNBits) - 1; - lBitMask = lMaxCode; - - // Code tables reset - - lBitPos = ResetBuf(lBitPos); - goto MainLoop; - } - - #endregion B - - #region C - - // setup - int inCode = code; - lStackP = lStack.Length; - - // Handle KwK case - if (code >= lFreeEnt) - { - if (code > lFreeEnt) - { - throw new LzwException("corrupt input: code=" + code + - ", freeEnt=" + lFreeEnt); - } - - lStack[--lStackP] = lFinChar; - code = lOldCode; - } - - // Generate output characters in reverse order - while (code >= 256) - { - lStack[--lStackP] = lTabSuffix[code]; - code = lTabPrefix[code]; - } - - lFinChar = lTabSuffix[code]; - buffer[offset++] = lFinChar; - count--; - - // And put them out in forward order - sSize = lStack.Length - lStackP; - int num = (sSize >= count) ? count : sSize; - Array.Copy(lStack, lStackP, buffer, offset, num); - offset += num; - count -= num; - lStackP += num; - - #endregion C - - #region D - - // generate new entry in table - if (lFreeEnt < lMaxMaxCode) - { - lTabPrefix[lFreeEnt] = lOldCode; - lTabSuffix[lFreeEnt] = lFinChar; - lFreeEnt++; - } - - // Remember previous code - lOldCode = inCode; - - // if output buffer full, then return - if (count == 0) - { - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - return offset - start; - } - - #endregion D - } // while - - lBitPos = ResetBuf(lBitPos); - } while (got > 0); // do..while - - nBits = lNBits; - maxCode = lMaxCode; - bitMask = lBitMask; - oldCode = lOldCode; - finChar = lFinChar; - stackP = lStackP; - freeEnt = lFreeEnt; - bitPos = lBitPos; - - eof = true; - return offset - start; - } - - /// - /// Moves the unread data in the buffer to the beginning and resets - /// the pointers. - /// - /// - /// - private int ResetBuf(int bitPosition) - { - int pos = bitPosition >> 3; - Array.Copy(data, pos, data, 0, end - pos); - end -= pos; - return 0; - } - - private void Fill() - { - got = baseInputStream.Read(data, end, data.Length - 1 - end); - if (got > 0) - { - end += got; - } - } - - private void ParseHeader() - { - headerParsed = true; - - byte[] hdr = new byte[LzwConstants.HDR_SIZE]; - - int result = baseInputStream.Read(hdr, 0, hdr.Length); - - // Check the magic marker - if (result < 0) - throw new LzwException("Failed to read LZW header"); - - if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) - { - throw new LzwException(String.Format( - "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", - hdr[0], hdr[1])); - } - - // Check the 3rd header byte - blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; - maxBits = hdr[2] & LzwConstants.BIT_MASK; - - if (maxBits > LzwConstants.MAX_BITS) - { - throw new LzwException("Stream compressed with " + maxBits + - " bits, but decompression can only handle " + - LzwConstants.MAX_BITS + " bits."); - } - - if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) - { - throw new LzwException("Unsupported bits set in the header."); - } - - // Initialize variables - maxMaxCode = 1 << maxBits; - nBits = LzwConstants.INIT_BITS; - maxCode = (1 << nBits) - 1; - bitMask = maxCode; - oldCode = -1; - finChar = 0; - freeEnt = blockMode ? TBL_FIRST : 256; - - tabPrefix = new int[1 << maxBits]; - tabSuffix = new byte[1 << maxBits]; - stack = new byte[1 << maxBits]; - stackP = stack.Length; - - for (int idx = 255; idx >= 0; idx--) - tabSuffix[idx] = (byte)idx; - } - - #region Stream Overrides - - /// - /// Gets a value indicating whether the current stream supports reading - /// - public override bool CanRead - { - get - { - return baseInputStream.CanRead; - } - } - - /// - /// Gets a value of false indicating seeking is not supported for this stream. - /// - public override bool CanSeek - { - get - { - return false; - } - } - - /// - /// Gets a value of false indicating that this stream is not writeable. - /// - public override bool CanWrite - { - get - { - return false; - } - } - - /// - /// A value representing the length of the stream in bytes. - /// - public override long Length - { - get - { - return got; - } - } - - /// - /// The current position within the stream. - /// Throws a NotSupportedException when attempting to set the position - /// - /// Attempting to set the position - public override long Position - { - get - { - return baseInputStream.Position; - } - set - { - throw new NotSupportedException("InflaterInputStream Position not supported"); - } - } - - /// - /// Flushes the baseInputStream - /// - public override void Flush() - { - baseInputStream.Flush(); - } - - /// - /// Sets the position within the current stream - /// Always throws a NotSupportedException - /// - /// The relative offset to seek to. - /// The defining where to seek from. - /// The new position in the stream. - /// Any access - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("Seek not supported"); - } - - /// - /// Set the length of the current stream - /// Always throws a NotSupportedException - /// - /// The new length value for the stream. - /// Any access - public override void SetLength(long value) - { - throw new NotSupportedException("InflaterInputStream SetLength not supported"); - } - - /// - /// Writes a sequence of bytes to stream and advances the current position - /// This method always throws a NotSupportedException - /// - /// The buffer containing data to write. - /// The offset of the first byte to write. - /// The number of bytes to write. - /// Any access - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("InflaterInputStream Write not supported"); - } - - /// - /// Writes one byte to the current stream and advances the current position - /// Always throws a NotSupportedException - /// - /// The byte to write. - /// Any access - public override void WriteByte(byte value) - { - throw new NotSupportedException("InflaterInputStream WriteByte not supported"); - } - - /// - /// Closes the input stream. When - /// is true the underlying stream is also closed. - /// - protected override void Dispose(bool disposing) - { - if (!isClosed) - { - isClosed = true; - if (IsStreamOwner) - { - baseInputStream.Dispose(); - } - } - } - - #endregion Stream Overrides - - #region Instance Fields - - private Stream baseInputStream; - - /// - /// Flag indicating wether this instance has been closed or not. - /// - private bool isClosed; - - private readonly byte[] one = new byte[1]; - private bool headerParsed; - - // string table stuff - private const int TBL_CLEAR = 0x100; - - private const int TBL_FIRST = TBL_CLEAR + 1; - - private int[] tabPrefix; - private byte[] tabSuffix; - private readonly int[] zeros = new int[256]; - private byte[] stack; - - // various state - private bool blockMode; - - private int nBits; - private int maxBits; - private int maxMaxCode; - private int maxCode; - private int bitMask; - private int oldCode; - private byte finChar; - private int stackP; - private int freeEnt; - - // input buffer - private readonly byte[] data = new byte[1024 * 8]; - - private int bitPos; - private int end; - private int got; - private bool eof; - private const int EXTRA = 64; - - #endregion Instance Fields - } + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Creates a LzwInputStream + /// + /// + /// The stream to read compressed data from (baseInputStream LZW format) + /// + public LzwInputStream(Stream baseInputStream) + { + this.baseInputStream = baseInputStream; + } + + /// + /// See + /// + /// + public override int ReadByte() + { + var b = Read(one, 0, 1); + return b == 1 ? one[0] & 0xff : -1; + } + + /// + /// Reads decompressed data into the provided buffer byte array + /// + /// + /// The array to read and decompress data into + /// + /// + /// The offset indicating where the data should be placed + /// + /// + /// The number of bytes to decompress + /// + /// The number of bytes read. Zero signals the end of stream + public override int Read(byte[] buffer, int offset, int count) + { + if (!headerParsed) + ParseHeader(); + + if (eof) + return 0; + + var start = offset; + + /* Using local copies of various variables speeds things up by as + * much as 30% in Java! Performance not tested in C#. + */ + var lTabPrefix = tabPrefix; + var lTabSuffix = tabSuffix; + var lStack = stack; + var lNBits = nBits; + var lMaxCode = maxCode; + var lMaxMaxCode = maxMaxCode; + var lBitMask = bitMask; + var lOldCode = oldCode; + var lFinChar = finChar; + var lStackP = stackP; + var lFreeEnt = freeEnt; + var lData = data; + var lBitPos = bitPos; + + // empty stack if stuff still left + var sSize = lStack.Length - lStackP; + if (sSize > 0) + { + var num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; + } + + if (count == 0) + { + stackP = lStackP; + return offset - start; + } + + // loop, filling local buffer until enough data has been decompressed + MainLoop: + do + { + if (end < EXTRA) + { + Fill(); + } + + var bitIn = (got > 0) ? (end - (end % lNBits)) << 3 : + (end << 3) - (lNBits - 1); + + while (lBitPos < bitIn) + { + #region A + + // handle 1-byte reads correctly + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + maxMaxCode = lMaxMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } + + // check for code-width expansion + if (lFreeEnt > lMaxCode) + { + var nBytes = lNBits << 3; + lBitPos = lBitPos - 1 + + nBytes - ((lBitPos - 1 + nBytes) % nBytes); + + lNBits++; + lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : + (1 << lNBits) - 1; + + lBitMask = (1 << lNBits) - 1; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } + + #endregion A + + #region B + + // read next code + var pos = lBitPos >> 3; + var code = (((lData[pos] & 0xFF) | + ((lData[pos + 1] & 0xFF) << 8) | + ((lData[pos + 2] & 0xFF) << 16)) >> + (lBitPos & 0x7)) & lBitMask; + + lBitPos += lNBits; + + // handle first iteration + if (lOldCode == -1) + { + if (code >= 256) + throw new LzwException("corrupt input: " + code + " > 255"); + + lFinChar = (byte)(lOldCode = code); + buffer[offset++] = lFinChar; + count--; + continue; + } + + // handle CLEAR code + if (code == TBL_CLEAR && blockMode) + { + Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); + lFreeEnt = TBL_FIRST - 1; + + var nBytes = lNBits << 3; + lBitPos = lBitPos - 1 + nBytes - ((lBitPos - 1 + nBytes) % nBytes); + lNBits = LzwConstants.INIT_BITS; + lMaxCode = (1 << lNBits) - 1; + lBitMask = lMaxCode; + + // Code tables reset + + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } + + #endregion B + + #region C + + // setup + var inCode = code; + lStackP = lStack.Length; + + // Handle KwK case + if (code >= lFreeEnt) + { + if (code > lFreeEnt) + { + throw new LzwException("corrupt input: code=" + code + + ", freeEnt=" + lFreeEnt); + } + + lStack[--lStackP] = lFinChar; + code = lOldCode; + } + + // Generate output characters in reverse order + while (code >= 256) + { + lStack[--lStackP] = lTabSuffix[code]; + code = lTabPrefix[code]; + } + + lFinChar = lTabSuffix[code]; + buffer[offset++] = lFinChar; + count--; + + // And put them out in forward order + sSize = lStack.Length - lStackP; + var num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; + + #endregion C + + #region D + + // generate new entry in table + if (lFreeEnt < lMaxMaxCode) + { + lTabPrefix[lFreeEnt] = lOldCode; + lTabSuffix[lFreeEnt] = lFinChar; + lFreeEnt++; + } + + // Remember previous code + lOldCode = inCode; + + // if output buffer full, then return + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } + + #endregion D + } // while + + lBitPos = ResetBuf(lBitPos); + } while (got > 0); // do..while + + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + eof = true; + return offset - start; + } + + /// + /// Moves the unread data in the buffer to the beginning and resets + /// the pointers. + /// + /// + /// + private int ResetBuf(int bitPosition) + { + var pos = bitPosition >> 3; + Array.Copy(data, pos, data, 0, end - pos); + end -= pos; + return 0; + } + + private void Fill() + { + got = baseInputStream.Read(data, end, data.Length - 1 - end); + if (got > 0) + { + end += got; + } + } + + private void ParseHeader() + { + headerParsed = true; + + var hdr = new byte[LzwConstants.HDR_SIZE]; + + var result = baseInputStream.Read(hdr, 0, hdr.Length); + + // Check the magic marker + if (result < 0) + throw new LzwException("Failed to read LZW header"); + + if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) + { + throw new LzwException(string.Format( + "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", + hdr[0], hdr[1])); + } + + // Check the 3rd header byte + blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; + maxBits = hdr[2] & LzwConstants.BIT_MASK; + + if (maxBits > LzwConstants.MAX_BITS) + { + throw new LzwException("Stream compressed with " + maxBits + + " bits, but decompression can only handle " + + LzwConstants.MAX_BITS + " bits."); + } + + if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) + { + throw new LzwException("Unsupported bits set in the header."); + } + + // Initialize variables + maxMaxCode = 1 << maxBits; + nBits = LzwConstants.INIT_BITS; + maxCode = (1 << nBits) - 1; + bitMask = maxCode; + oldCode = -1; + finChar = 0; + freeEnt = blockMode ? TBL_FIRST : 256; + + tabPrefix = new int[1 << maxBits]; + tabSuffix = new byte[1 << maxBits]; + stack = new byte[1 << maxBits]; + stackP = stack.Length; + + for (var idx = 255; idx >= 0; idx--) + tabSuffix[idx] = (byte)idx; + } + + #region Stream Overrides + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return baseInputStream.CanRead; + } + } + + /// + /// Gets a value of false indicating seeking is not supported for this stream. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value of false indicating that this stream is not writeable. + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// A value representing the length of the stream in bytes. + /// + public override long Length + { + get + { + return got; + } + } + + /// + /// The current position within the stream. + /// Throws a NotSupportedException when attempting to set the position + /// + /// Attempting to set the position + public override long Position + { + get + { + return baseInputStream.Position; + } + set + { + throw new NotSupportedException("InflaterInputStream Position not supported"); + } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + baseInputStream.Flush(); + } + + /// + /// Sets the position within the current stream + /// Always throws a NotSupportedException + /// + /// The relative offset to seek to. + /// The defining where to seek from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported"); + } + + /// + /// Set the length of the current stream + /// Always throws a NotSupportedException + /// + /// The new length value for the stream. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("InflaterInputStream SetLength not supported"); + } + + /// + /// Writes a sequence of bytes to stream and advances the current position + /// This method always throws a NotSupportedException + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("InflaterInputStream Write not supported"); + } + + /// + /// Writes one byte to the current stream and advances the current position + /// Always throws a NotSupportedException + /// + /// The byte to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("InflaterInputStream WriteByte not supported"); + } + + /// + /// Closes the input stream. When + /// is true the underlying stream is also closed. + /// + protected override void Dispose(bool disposing) + { + if (!isClosed) + { + isClosed = true; + if (IsStreamOwner) + { + baseInputStream.Dispose(); + } + } + } + + #endregion Stream Overrides + + #region Instance Fields + + private readonly Stream baseInputStream; + + /// + /// Flag indicating wether this instance has been closed or not. + /// + private bool isClosed; + + private readonly byte[] one = new byte[1]; + private bool headerParsed; + + // string table stuff + private const int TBL_CLEAR = 0x100; + + private const int TBL_FIRST = TBL_CLEAR + 1; + + private int[] tabPrefix; + private byte[] tabSuffix; + private readonly int[] zeros = new int[256]; + private byte[] stack; + + // various state + private bool blockMode; + + private int nBits; + private int maxBits; + private int maxMaxCode; + private int maxCode; + private int bitMask; + private int oldCode; + private byte finChar; + private int stackP; + private int freeEnt; + + // input buffer + private readonly byte[] data = new byte[1024 * 8]; + + private int bitPos; + private int end; + private int got; + private bool eof; + private const int EXTRA = 64; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs index a503a551e..21bf059a0 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs @@ -1,55 +1,54 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// This exception is used to indicate that there is a problem +/// with a TAR archive header. +/// +[Serializable] +public class InvalidHeaderException : TarException { - /// - /// This exception is used to indicate that there is a problem - /// with a TAR archive header. - /// - [Serializable] - public class InvalidHeaderException : TarException - { - /// - /// Initialise a new instance of the InvalidHeaderException class. - /// - public InvalidHeaderException() - { - } + /// + /// Initialise a new instance of the InvalidHeaderException class. + /// + public InvalidHeaderException() + { + } - /// - /// Initialises a new instance of the InvalidHeaderException class with a specified message. - /// - /// Message describing the exception cause. - public InvalidHeaderException(string message) - : base(message) - { - } + /// + /// Initialises a new instance of the InvalidHeaderException class with a specified message. + /// + /// Message describing the exception cause. + public InvalidHeaderException(string message) + : base(message) + { + } - /// - /// Initialise a new instance of InvalidHeaderException - /// - /// Message describing the problem. - /// The exception that is the cause of the current exception. - public InvalidHeaderException(string message, Exception exception) - : base(message, exception) - { - } + /// + /// Initialise a new instance of InvalidHeaderException + /// + /// Message describing the problem. + /// The exception that is the cause of the current exception. + public InvalidHeaderException(string message, Exception exception) + : base(message, exception) + { + } - /// - /// Initializes a new instance of the InvalidHeaderException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected InvalidHeaderException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the InvalidHeaderException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected InvalidHeaderException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs index b1b94c35b..696b9d9ae 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs @@ -1,1027 +1,943 @@ +using MelonLoader.ICSharpCode.SharpZipLib.Core; using System; using System.IO; using System.Text; -using MelonLoader.ICSharpCode.SharpZipLib.Core; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// Used to advise clients of 'events' while processing archives +/// +public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message); + +/// +/// The TarArchive class implements the concept of a +/// 'Tape Archive'. A tar archive is a series of entries, each of +/// which represents a file system object. Each entry in +/// the archive consists of a header block followed by 0 or more data blocks. +/// Directory entries consist only of the header block, and are followed by entries +/// for the directory's contents. File entries consist of a +/// header followed by the number of blocks needed to +/// contain the file's contents. All entries are written on +/// block boundaries. Blocks are 512 bytes long. +/// +/// TarArchives are instantiated in either read or write mode, +/// based upon whether they are instantiated with an InputStream +/// or an OutputStream. Once instantiated TarArchives read/write +/// mode can not be changed. +/// +/// There is currently no support for random access to tar archives. +/// However, it seems that subclassing TarArchive, and using the +/// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock +/// properties, this would be rather trivial. +/// +public class TarArchive : IDisposable { - /// - /// Used to advise clients of 'events' while processing archives - /// - public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message); - - /// - /// The TarArchive class implements the concept of a - /// 'Tape Archive'. A tar archive is a series of entries, each of - /// which represents a file system object. Each entry in - /// the archive consists of a header block followed by 0 or more data blocks. - /// Directory entries consist only of the header block, and are followed by entries - /// for the directory's contents. File entries consist of a - /// header followed by the number of blocks needed to - /// contain the file's contents. All entries are written on - /// block boundaries. Blocks are 512 bytes long. - /// - /// TarArchives are instantiated in either read or write mode, - /// based upon whether they are instantiated with an InputStream - /// or an OutputStream. Once instantiated TarArchives read/write - /// mode can not be changed. - /// - /// There is currently no support for random access to tar archives. - /// However, it seems that subclassing TarArchive, and using the - /// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock - /// properties, this would be rather trivial. - /// - public class TarArchive : IDisposable - { - /// - /// Client hook allowing detailed information to be reported during processing - /// - public event ProgressMessageHandler ProgressMessageEvent; - - /// - /// Raises the ProgressMessage event - /// - /// The TarEntry for this event - /// message for this event. Null is no message - protected virtual void OnProgressMessageEvent(TarEntry entry, string message) - { - ProgressMessageHandler handler = ProgressMessageEvent; - if (handler != null) - { - handler(this, entry, message); - } - } - - #region Constructors - - /// - /// Constructor for a default . - /// - protected TarArchive() - { - } - - /// - /// Initialise a TarArchive for input. - /// - /// The to use for input. - protected TarArchive(TarInputStream stream) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - tarIn = stream; - } - - /// - /// Initialise a TarArchive for output. - /// - /// The to use for output. - protected TarArchive(TarOutputStream stream) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - tarOut = stream; - } - - #endregion Constructors - - #region Static factory methods - - /// - /// The InputStream based constructors create a TarArchive for the - /// purposes of extracting or listing a tar archive. Thus, use - /// these constructors when you wish to extract files from or list - /// the contents of an existing tar archive. - /// - /// The stream to retrieve archive data from. - /// Returns a new suitable for reading from. - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public static TarArchive CreateInputTarArchive(Stream inputStream) - { - return CreateInputTarArchive(inputStream, null); - } - - /// - /// The InputStream based constructors create a TarArchive for the - /// purposes of extracting or listing a tar archive. Thus, use - /// these constructors when you wish to extract files from or list - /// the contents of an existing tar archive. - /// - /// The stream to retrieve archive data from. - /// The used for the Name fields, or null for ASCII only - /// Returns a new suitable for reading from. - public static TarArchive CreateInputTarArchive(Stream inputStream, Encoding nameEncoding) - { - if (inputStream == null) - { - throw new ArgumentNullException(nameof(inputStream)); - } - - var tarStream = inputStream as TarInputStream; - - TarArchive result; - if (tarStream != null) - { - result = new TarArchive(tarStream); - } - else - { - result = CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding); - } - return result; - } - - /// - /// Create TarArchive for reading setting block factor - /// - /// A stream containing the tar archive contents - /// The blocking factor to apply - /// Returns a suitable for reading. - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor) - { - return CreateInputTarArchive(inputStream, blockFactor, null); - } - - /// - /// Create TarArchive for reading setting block factor - /// - /// A stream containing the tar archive contents - /// The blocking factor to apply - /// The used for the Name fields, or null for ASCII only - /// Returns a suitable for reading. - public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor, Encoding nameEncoding) - { - if (inputStream == null) - { - throw new ArgumentNullException(nameof(inputStream)); - } - - if (inputStream is TarInputStream) - { - throw new ArgumentException("TarInputStream not valid"); - } - - return new TarArchive(new TarInputStream(inputStream, blockFactor, nameEncoding)); - } - /// - /// Create a TarArchive for writing to, using the default blocking factor - /// - /// The to write to - /// The used for the Name fields, or null for ASCII only - /// Returns a suitable for writing. - public static TarArchive CreateOutputTarArchive(Stream outputStream, Encoding nameEncoding) - { - if (outputStream == null) - { - throw new ArgumentNullException(nameof(outputStream)); - } - - var tarStream = outputStream as TarOutputStream; - - TarArchive result; - if (tarStream != null) - { - result = new TarArchive(tarStream); - } - else - { - result = CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding); - } - return result; - } - /// - /// Create a TarArchive for writing to, using the default blocking factor - /// - /// The to write to - /// Returns a suitable for writing. - public static TarArchive CreateOutputTarArchive(Stream outputStream) - { - return CreateOutputTarArchive(outputStream, null); - } - - /// - /// Create a tar archive for writing. - /// - /// The stream to write to - /// The blocking factor to use for buffering. - /// Returns a suitable for writing. - public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor) - { - return CreateOutputTarArchive(outputStream, blockFactor, null); - } - /// - /// Create a tar archive for writing. - /// - /// The stream to write to - /// The blocking factor to use for buffering. - /// The used for the Name fields, or null for ASCII only - /// Returns a suitable for writing. - public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor, Encoding nameEncoding) - { - if (outputStream == null) - { - throw new ArgumentNullException(nameof(outputStream)); - } - - if (outputStream is TarOutputStream) - { - throw new ArgumentException("TarOutputStream is not valid"); - } - - return new TarArchive(new TarOutputStream(outputStream, blockFactor, nameEncoding)); - } - - #endregion Static factory methods - - /// - /// Set the flag that determines whether existing files are - /// kept, or overwritten during extraction. - /// - /// - /// If true, do not overwrite existing files. - /// - public void SetKeepOldFiles(bool keepExistingFiles) - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - keepOldFiles = keepExistingFiles; - } - - /// - /// Get/set the ascii file translation flag. If ascii file translation - /// is true, then the file is checked to see if it a binary file or not. - /// If the flag is true and the test indicates it is ascii text - /// file, it will be translated. The translation converts the local - /// operating system's concept of line ends into the UNIX line end, - /// '\n', which is the defacto standard for a TAR archive. This makes - /// text files compatible with UNIX. - /// - public bool AsciiTranslate - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return asciiTranslate; - } - - set - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - asciiTranslate = value; - } - } - - /// - /// Set the ascii file translation flag. - /// - /// - /// If true, translate ascii text files. - /// - [Obsolete("Use the AsciiTranslate property")] - public void SetAsciiTranslation(bool translateAsciiFiles) - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - asciiTranslate = translateAsciiFiles; - } - - /// - /// PathPrefix is added to entry names as they are written if the value is not null. - /// A slash character is appended after PathPrefix - /// - public string PathPrefix - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return pathPrefix; - } - - set - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - pathPrefix = value; - } - } - - /// - /// RootPath is removed from entry names if it is found at the - /// beginning of the name. - /// - public string RootPath - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return rootPath; - } - - set - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - // Convert to forward slashes for matching. Trim trailing / for correct final path - rootPath = value.Replace('\\', '/').TrimEnd('/'); - } - } - - /// - /// Set user and group information that will be used to fill in the - /// tar archive's entry headers. This information is based on that available - /// for the linux operating system, which is not always available on other - /// operating systems. TarArchive allows the programmer to specify values - /// to be used in their place. - /// is set to true by this call. - /// - /// - /// The user id to use in the headers. - /// - /// - /// The user name to use in the headers. - /// - /// - /// The group id to use in the headers. - /// - /// - /// The group name to use in the headers. - /// - public void SetUserInfo(int userId, string userName, int groupId, string groupName) - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - this.userId = userId; - this.userName = userName; - this.groupId = groupId; - this.groupName = groupName; - applyUserInfoOverrides = true; - } - - /// - /// Get or set a value indicating if overrides defined by SetUserInfo should be applied. - /// - /// If overrides are not applied then the values as set in each header will be used. - public bool ApplyUserInfoOverrides - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return applyUserInfoOverrides; - } - - set - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - applyUserInfoOverrides = value; - } - } - - /// - /// Get the archive user id. - /// See ApplyUserInfoOverrides for detail - /// on how to allow setting values on a per entry basis. - /// - /// - /// The current user id. - /// - public int UserId - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return userId; - } - } - - /// - /// Get the archive user name. - /// See ApplyUserInfoOverrides for detail - /// on how to allow setting values on a per entry basis. - /// - /// - /// The current user name. - /// - public string UserName - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return userName; - } - } - - /// - /// Get the archive group id. - /// See ApplyUserInfoOverrides for detail - /// on how to allow setting values on a per entry basis. - /// - /// - /// The current group id. - /// - public int GroupId - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return groupId; - } - } - - /// - /// Get the archive group name. - /// See ApplyUserInfoOverrides for detail - /// on how to allow setting values on a per entry basis. - /// - /// - /// The current group name. - /// - public string GroupName - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - return groupName; - } - } - - /// - /// Get the archive's record size. Tar archives are composed of - /// a series of RECORDS each containing a number of BLOCKS. - /// This allowed tar archives to match the IO characteristics of - /// the physical device being used. Archives are expected - /// to be properly "blocked". - /// - /// - /// The record size this archive is using. - /// - public int RecordSize - { - get - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - if (tarIn != null) - { - return tarIn.RecordSize; - } - else if (tarOut != null) - { - return tarOut.RecordSize; - } - return TarBuffer.DefaultRecordSize; - } - } - - /// - /// Sets the IsStreamOwner property on the underlying stream. - /// Set this to false to prevent the Close of the TarArchive from closing the stream. - /// - public bool IsStreamOwner - { - set - { - if (tarIn != null) - { - tarIn.IsStreamOwner = value; - } - else - { - tarOut.IsStreamOwner = value; - } - } - } - - /// - /// Close the archive. - /// - [Obsolete("Use Close instead")] - public void CloseArchive() - { - Close(); - } - - /// - /// Perform the "list" command for the archive contents. - /// - /// NOTE That this method uses the progress event to actually list - /// the contents. If the progress display event is not set, nothing will be listed! - /// - public void ListContents() - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - while (true) - { - TarEntry entry = tarIn.GetNextEntry(); - - if (entry == null) - { - break; - } - OnProgressMessageEvent(entry, null); - } - } - - /// - /// Perform the "extract" command and extract the contents of the archive. - /// - /// - /// The destination directory into which to extract. - /// - public void ExtractContents(string destinationDirectory) - => ExtractContents(destinationDirectory, false); - - /// - /// Perform the "extract" command and extract the contents of the archive. - /// - /// - /// The destination directory into which to extract. - /// - /// Allow parent directory traversal in file paths (e.g. ../file) - public void ExtractContents(string destinationDirectory, bool allowParentTraversal) - { - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - var fullDistDir = Path.GetFullPath(destinationDirectory); - - while (true) - { - TarEntry entry = tarIn.GetNextEntry(); - - if (entry == null) - { - break; - } - - if (entry.TarHeader.TypeFlag == TarHeader.LF_LINK || entry.TarHeader.TypeFlag == TarHeader.LF_SYMLINK) - continue; - - ExtractEntry(fullDistDir, entry, allowParentTraversal); - } - } - - /// - /// Extract an entry from the archive. This method assumes that the - /// tarIn stream has been properly set with a call to GetNextEntry(). - /// - /// - /// The destination directory into which to extract. - /// - /// - /// The TarEntry returned by tarIn.GetNextEntry(). - /// - /// Allow parent directory traversal in file paths (e.g. ../file) - private void ExtractEntry(string destDir, TarEntry entry, bool allowParentTraversal) - { - OnProgressMessageEvent(entry, null); - - string name = entry.Name; - - if (Path.IsPathRooted(name)) - { - // NOTE: - // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt - name = name.Substring(Path.GetPathRoot(name).Length); - } - - name = name.Replace('/', Path.DirectorySeparatorChar); - - string destFile = Path.Combine(destDir, name); - var destFileDir = Path.GetDirectoryName(Path.GetFullPath(destFile)) ?? ""; - - if (!allowParentTraversal && !destFileDir.StartsWith(destDir, StringComparison.InvariantCultureIgnoreCase)) - { - throw new InvalidNameException("Parent traversal in paths is not allowed"); - } - - if (entry.IsDirectory) - { - EnsureDirectoryExists(destFile); - } - else - { - string parentDirectory = Path.GetDirectoryName(destFile); - EnsureDirectoryExists(parentDirectory); - - bool process = true; - var fileInfo = new FileInfo(destFile); - if (fileInfo.Exists) - { - if (keepOldFiles) - { - OnProgressMessageEvent(entry, "Destination file already exists"); - process = false; - } - else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0) - { - OnProgressMessageEvent(entry, "Destination file already exists, and is read-only"); - process = false; - } - } - - if (process) - { - using (var outputStream = File.Create(destFile)) - { - if (this.asciiTranslate) - { - // May need to translate the file. - ExtractAndTranslateEntry(destFile, outputStream); - } - else - { - // If translation is disabled, just copy the entry across directly. - tarIn.CopyEntryContents(outputStream); - } - } - } - } - } - - // Extract a TAR entry, and perform an ASCII translation if required. - private void ExtractAndTranslateEntry(string destFile, Stream outputStream) - { - bool asciiTrans = !IsBinary(destFile); - - if (asciiTrans) - { - using (var outw = new StreamWriter(outputStream, new UTF8Encoding(false), 1024)) - { - byte[] rdbuf = new byte[32 * 1024]; - - while (true) - { - int numRead = tarIn.Read(rdbuf, 0, rdbuf.Length); - - if (numRead <= 0) - { - break; - } - - for (int off = 0, b = 0; b < numRead; ++b) - { - if (rdbuf[b] == 10) - { - string s = Encoding.ASCII.GetString(rdbuf, off, (b - off)); - outw.WriteLine(s); - off = b + 1; - } - } - } - } - } - else - { - // No translation required. - tarIn.CopyEntryContents(outputStream); - } - } - - /// - /// Write an entry to the archive. This method will call the putNextEntry - /// and then write the contents of the entry, and finally call closeEntry() - /// for entries that are files. For directories, it will call putNextEntry(), - /// and then, if the recurse flag is true, process each entry that is a - /// child of the directory. - /// - /// - /// The TarEntry representing the entry to write to the archive. - /// - /// - /// If true, process the children of directory entries. - /// - public void WriteEntry(TarEntry sourceEntry, bool recurse) - { - if (sourceEntry == null) - { - throw new ArgumentNullException(nameof(sourceEntry)); - } - - if (isDisposed) - { - throw new ObjectDisposedException("TarArchive"); - } - - try - { - if (recurse) - { - TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName, - sourceEntry.GroupId, sourceEntry.GroupName); - } - WriteEntryCore(sourceEntry, recurse); - } - finally - { - if (recurse) - { - TarHeader.RestoreSetValues(); - } - } - } - - /// - /// Write an entry to the archive. This method will call the putNextEntry - /// and then write the contents of the entry, and finally call closeEntry() - /// for entries that are files. For directories, it will call putNextEntry(), - /// and then, if the recurse flag is true, process each entry that is a - /// child of the directory. - /// - /// - /// The TarEntry representing the entry to write to the archive. - /// - /// - /// If true, process the children of directory entries. - /// - private void WriteEntryCore(TarEntry sourceEntry, bool recurse) - { - string tempFileName = null; - string entryFilename = sourceEntry.File; - - var entry = (TarEntry)sourceEntry.Clone(); - - if (applyUserInfoOverrides) - { - entry.GroupId = groupId; - entry.GroupName = groupName; - entry.UserId = userId; - entry.UserName = userName; - } - - OnProgressMessageEvent(entry, null); - - if (asciiTranslate && !entry.IsDirectory) - { - if (!IsBinary(entryFilename)) - { - tempFileName = PathUtils.GetTempFileName(); - - using (StreamReader inStream = File.OpenText(entryFilename)) - { - using (Stream outStream = File.Create(tempFileName)) - { - while (true) - { - string line = inStream.ReadLine(); - if (line == null) - { - break; - } - byte[] data = Encoding.ASCII.GetBytes(line); - outStream.Write(data, 0, data.Length); - outStream.WriteByte((byte)'\n'); - } - - outStream.Flush(); - } - } - - entry.Size = new FileInfo(tempFileName).Length; - entryFilename = tempFileName; - } - } - - string newName = null; - - if (!String.IsNullOrEmpty(rootPath)) - { - if (entry.Name.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) - { - newName = entry.Name.Substring(rootPath.Length + 1); - } - } - - if (pathPrefix != null) - { - newName = (newName == null) ? pathPrefix + "/" + entry.Name : pathPrefix + "/" + newName; - } - - if (newName != null) - { - entry.Name = newName; - } - - tarOut.PutNextEntry(entry); - - if (entry.IsDirectory) - { - if (recurse) - { - TarEntry[] list = entry.GetDirectoryEntries(); - for (int i = 0; i < list.Length; ++i) - { - WriteEntryCore(list[i], recurse); - } - } - } - else - { - using (Stream inputStream = File.OpenRead(entryFilename)) - { - byte[] localBuffer = new byte[32 * 1024]; - while (true) - { - int numRead = inputStream.Read(localBuffer, 0, localBuffer.Length); - - if (numRead <= 0) - { - break; - } - - tarOut.Write(localBuffer, 0, numRead); - } - } - - if (!string.IsNullOrEmpty(tempFileName)) - { - File.Delete(tempFileName); - } - - tarOut.CloseEntry(); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases the unmanaged resources used by the FileStream and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; - /// false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (!isDisposed) - { - isDisposed = true; - if (disposing) - { - if (tarOut != null) - { - tarOut.Flush(); - tarOut.Dispose(); - } - - if (tarIn != null) - { - tarIn.Dispose(); - } - } - } - } - - /// - /// Closes the archive and releases any associated resources. - /// - public virtual void Close() - { - Dispose(true); - } - - /// - /// Ensures that resources are freed and other cleanup operations are performed - /// when the garbage collector reclaims the . - /// - ~TarArchive() - { - Dispose(false); - } - - private static void EnsureDirectoryExists(string directoryName) - { - if (!Directory.Exists(directoryName)) - { - try - { - Directory.CreateDirectory(directoryName); - } - catch (Exception e) - { - throw new TarException("Exception creating directory '" + directoryName + "', " + e.Message, e); - } - } - } - - // TODO: TarArchive - Is there a better way to test for a text file? - // It no longer reads entire files into memory but is still a weak test! - // This assumes that byte values 0-7, 14-31 or 255 are binary - // and that all non text files contain one of these values - private static bool IsBinary(string filename) - { - using (FileStream fs = File.OpenRead(filename)) - { - int sampleSize = Math.Min(4096, (int)fs.Length); - byte[] content = new byte[sampleSize]; - - int bytesRead = fs.Read(content, 0, sampleSize); - - for (int i = 0; i < bytesRead; ++i) - { - byte b = content[i]; - if ((b < 8) || ((b > 13) && (b < 32)) || (b == 255)) - { - return true; - } - } - } - return false; - } - - #region Instance Fields - - private bool keepOldFiles; - private bool asciiTranslate; - - private int userId; - private string userName = string.Empty; - private int groupId; - private string groupName = string.Empty; - - private string rootPath; - private string pathPrefix; - - private bool applyUserInfoOverrides; - - private TarInputStream tarIn; - private TarOutputStream tarOut; - private bool isDisposed; - - #endregion Instance Fields - } + /// + /// Client hook allowing detailed information to be reported during processing + /// + public event ProgressMessageHandler ProgressMessageEvent; + + /// + /// Raises the ProgressMessage event + /// + /// The TarEntry for this event + /// message for this event. Null is no message + protected virtual void OnProgressMessageEvent(TarEntry entry, string message) + { + var handler = ProgressMessageEvent; + if (handler != null) + { + handler(this, entry, message); + } + } + + #region Constructors + + /// + /// Constructor for a default . + /// + protected TarArchive() + { + } + + /// + /// Initialise a TarArchive for input. + /// + /// The to use for input. + protected TarArchive(TarInputStream stream) + { + tarIn = stream ?? throw new ArgumentNullException(nameof(stream)); + } + + /// + /// Initialise a TarArchive for output. + /// + /// The to use for output. + protected TarArchive(TarOutputStream stream) + { + tarOut = stream ?? throw new ArgumentNullException(nameof(stream)); + } + + #endregion Constructors + + #region Static factory methods + + /// + /// The InputStream based constructors create a TarArchive for the + /// purposes of extracting or listing a tar archive. Thus, use + /// these constructors when you wish to extract files from or list + /// the contents of an existing tar archive. + /// + /// The stream to retrieve archive data from. + /// Returns a new suitable for reading from. + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public static TarArchive CreateInputTarArchive(Stream inputStream) + { + return CreateInputTarArchive(inputStream, null); + } + + /// + /// The InputStream based constructors create a TarArchive for the + /// purposes of extracting or listing a tar archive. Thus, use + /// these constructors when you wish to extract files from or list + /// the contents of an existing tar archive. + /// + /// The stream to retrieve archive data from. + /// The used for the Name fields, or null for ASCII only + /// Returns a new suitable for reading from. + public static TarArchive CreateInputTarArchive(Stream inputStream, Encoding nameEncoding) + { + if (inputStream == null) + { + throw new ArgumentNullException(nameof(inputStream)); + } + + + var result = inputStream is TarInputStream tarStream ? new TarArchive(tarStream) : CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding); + return result; + } + + /// + /// Create TarArchive for reading setting block factor + /// + /// A stream containing the tar archive contents + /// The blocking factor to apply + /// Returns a suitable for reading. + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor) + { + return CreateInputTarArchive(inputStream, blockFactor, null); + } + + /// + /// Create TarArchive for reading setting block factor + /// + /// A stream containing the tar archive contents + /// The blocking factor to apply + /// The used for the Name fields, or null for ASCII only + /// Returns a suitable for reading. + public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor, Encoding nameEncoding) + { + if (inputStream == null) + { + throw new ArgumentNullException(nameof(inputStream)); + } + + return inputStream is TarInputStream + ? throw new ArgumentException("TarInputStream not valid") + : new TarArchive(new TarInputStream(inputStream, blockFactor, nameEncoding)); + } + /// + /// Create a TarArchive for writing to, using the default blocking factor + /// + /// The to write to + /// The used for the Name fields, or null for ASCII only + /// Returns a suitable for writing. + public static TarArchive CreateOutputTarArchive(Stream outputStream, Encoding nameEncoding) + { + if (outputStream == null) + { + throw new ArgumentNullException(nameof(outputStream)); + } + + + var result = outputStream is TarOutputStream tarStream + ? new TarArchive(tarStream) + : CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding); + return result; + } + /// + /// Create a TarArchive for writing to, using the default blocking factor + /// + /// The to write to + /// Returns a suitable for writing. + public static TarArchive CreateOutputTarArchive(Stream outputStream) + { + return CreateOutputTarArchive(outputStream, null); + } + + /// + /// Create a tar archive for writing. + /// + /// The stream to write to + /// The blocking factor to use for buffering. + /// Returns a suitable for writing. + public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor) + { + return CreateOutputTarArchive(outputStream, blockFactor, null); + } + /// + /// Create a tar archive for writing. + /// + /// The stream to write to + /// The blocking factor to use for buffering. + /// The used for the Name fields, or null for ASCII only + /// Returns a suitable for writing. + public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor, Encoding nameEncoding) + { + if (outputStream == null) + { + throw new ArgumentNullException(nameof(outputStream)); + } + + return outputStream is TarOutputStream + ? throw new ArgumentException("TarOutputStream is not valid") + : new TarArchive(new TarOutputStream(outputStream, blockFactor, nameEncoding)); + } + + #endregion Static factory methods + + /// + /// Set the flag that determines whether existing files are + /// kept, or overwritten during extraction. + /// + /// + /// If true, do not overwrite existing files. + /// + public void SetKeepOldFiles(bool keepExistingFiles) + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + keepOldFiles = keepExistingFiles; + } + + /// + /// Get/set the ascii file translation flag. If ascii file translation + /// is true, then the file is checked to see if it a binary file or not. + /// If the flag is true and the test indicates it is ascii text + /// file, it will be translated. The translation converts the local + /// operating system's concept of line ends into the UNIX line end, + /// '\n', which is the defacto standard for a TAR archive. This makes + /// text files compatible with UNIX. + /// + public bool AsciiTranslate + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : asciiTranslate; + } + + set + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + asciiTranslate = value; + } + } + + /// + /// Set the ascii file translation flag. + /// + /// + /// If true, translate ascii text files. + /// + [Obsolete("Use the AsciiTranslate property")] + public void SetAsciiTranslation(bool translateAsciiFiles) + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + asciiTranslate = translateAsciiFiles; + } + + /// + /// PathPrefix is added to entry names as they are written if the value is not null. + /// A slash character is appended after PathPrefix + /// + public string PathPrefix + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : pathPrefix; + } + + set + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + pathPrefix = value; + } + } + + /// + /// RootPath is removed from entry names if it is found at the + /// beginning of the name. + /// + public string RootPath + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : rootPath; + } + + set + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + // Convert to forward slashes for matching. Trim trailing / for correct final path + rootPath = value.Replace('\\', '/').TrimEnd('/'); + } + } + + /// + /// Set user and group information that will be used to fill in the + /// tar archive's entry headers. This information is based on that available + /// for the linux operating system, which is not always available on other + /// operating systems. TarArchive allows the programmer to specify values + /// to be used in their place. + /// is set to true by this call. + /// + /// + /// The user id to use in the headers. + /// + /// + /// The user name to use in the headers. + /// + /// + /// The group id to use in the headers. + /// + /// + /// The group name to use in the headers. + /// + public void SetUserInfo(int userId, string userName, int groupId, string groupName) + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + this.userId = userId; + this.userName = userName; + this.groupId = groupId; + this.groupName = groupName; + applyUserInfoOverrides = true; + } + + /// + /// Get or set a value indicating if overrides defined by SetUserInfo should be applied. + /// + /// If overrides are not applied then the values as set in each header will be used. + public bool ApplyUserInfoOverrides + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : applyUserInfoOverrides; + } + + set + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + applyUserInfoOverrides = value; + } + } + + /// + /// Get the archive user id. + /// See ApplyUserInfoOverrides for detail + /// on how to allow setting values on a per entry basis. + /// + /// + /// The current user id. + /// + public int UserId + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : userId; + } + } + + /// + /// Get the archive user name. + /// See ApplyUserInfoOverrides for detail + /// on how to allow setting values on a per entry basis. + /// + /// + /// The current user name. + /// + public string UserName + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : userName; + } + } + + /// + /// Get the archive group id. + /// See ApplyUserInfoOverrides for detail + /// on how to allow setting values on a per entry basis. + /// + /// + /// The current group id. + /// + public int GroupId + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : groupId; + } + } + + /// + /// Get the archive group name. + /// See ApplyUserInfoOverrides for detail + /// on how to allow setting values on a per entry basis. + /// + /// + /// The current group name. + /// + public string GroupName + { + get + { + return isDisposed ? throw new ObjectDisposedException("TarArchive") : groupName; + } + } + + /// + /// Get the archive's record size. Tar archives are composed of + /// a series of RECORDS each containing a number of BLOCKS. + /// This allowed tar archives to match the IO characteristics of + /// the physical device being used. Archives are expected + /// to be properly "blocked". + /// + /// + /// The record size this archive is using. + /// + public int RecordSize + { + get + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + if (tarIn != null) + { + return tarIn.RecordSize; + } + else if (tarOut != null) + { + return tarOut.RecordSize; + } + return TarBuffer.DefaultRecordSize; + } + } + + /// + /// Sets the IsStreamOwner property on the underlying stream. + /// Set this to false to prevent the Close of the TarArchive from closing the stream. + /// + public bool IsStreamOwner + { + set + { + if (tarIn != null) + { + tarIn.IsStreamOwner = value; + } + else + { + tarOut.IsStreamOwner = value; + } + } + } + + /// + /// Close the archive. + /// + [Obsolete("Use Close instead")] + public void CloseArchive() + { + Close(); + } + + /// + /// Perform the "list" command for the archive contents. + /// + /// NOTE That this method uses the progress event to actually list + /// the contents. If the progress display event is not set, nothing will be listed! + /// + public void ListContents() + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + while (true) + { + var entry = tarIn.GetNextEntry(); + + if (entry == null) + { + break; + } + OnProgressMessageEvent(entry, null); + } + } + + /// + /// Perform the "extract" command and extract the contents of the archive. + /// + /// + /// The destination directory into which to extract. + /// + public void ExtractContents(string destinationDirectory) + => ExtractContents(destinationDirectory, false); + + /// + /// Perform the "extract" command and extract the contents of the archive. + /// + /// + /// The destination directory into which to extract. + /// + /// Allow parent directory traversal in file paths (e.g. ../file) + public void ExtractContents(string destinationDirectory, bool allowParentTraversal) + { + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + var fullDistDir = Path.GetFullPath(destinationDirectory); + + while (true) + { + var entry = tarIn.GetNextEntry(); + + if (entry == null) + { + break; + } + + if (entry.TarHeader.TypeFlag is TarHeader.LF_LINK or TarHeader.LF_SYMLINK) + continue; + + ExtractEntry(fullDistDir, entry, allowParentTraversal); + } + } + + /// + /// Extract an entry from the archive. This method assumes that the + /// tarIn stream has been properly set with a call to GetNextEntry(). + /// + /// + /// The destination directory into which to extract. + /// + /// + /// The TarEntry returned by tarIn.GetNextEntry(). + /// + /// Allow parent directory traversal in file paths (e.g. ../file) + private void ExtractEntry(string destDir, TarEntry entry, bool allowParentTraversal) + { + OnProgressMessageEvent(entry, null); + + var name = entry.Name; + + if (Path.IsPathRooted(name)) + { + // NOTE: + // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt + name = name[Path.GetPathRoot(name).Length..]; + } + + name = name.Replace('/', Path.DirectorySeparatorChar); + + var destFile = Path.Combine(destDir, name); + var destFileDir = Path.GetDirectoryName(Path.GetFullPath(destFile)) ?? ""; + + if (!allowParentTraversal && !destFileDir.StartsWith(destDir, StringComparison.InvariantCultureIgnoreCase)) + { + throw new InvalidNameException("Parent traversal in paths is not allowed"); + } + + if (entry.IsDirectory) + { + EnsureDirectoryExists(destFile); + } + else + { + var parentDirectory = Path.GetDirectoryName(destFile); + EnsureDirectoryExists(parentDirectory); + + var process = true; + var fileInfo = new FileInfo(destFile); + if (fileInfo.Exists) + { + if (keepOldFiles) + { + OnProgressMessageEvent(entry, "Destination file already exists"); + process = false; + } + else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0) + { + OnProgressMessageEvent(entry, "Destination file already exists, and is read-only"); + process = false; + } + } + + if (process) + { + using var outputStream = File.Create(destFile); + if (this.asciiTranslate) + { + // May need to translate the file. + ExtractAndTranslateEntry(destFile, outputStream); + } + else + { + // If translation is disabled, just copy the entry across directly. + tarIn.CopyEntryContents(outputStream); + } + } + } + } + + // Extract a TAR entry, and perform an ASCII translation if required. + private void ExtractAndTranslateEntry(string destFile, Stream outputStream) + { + var asciiTrans = !IsBinary(destFile); + + if (asciiTrans) + { + using var outw = new StreamWriter(outputStream, new UTF8Encoding(false), 1024); + var rdbuf = new byte[32 * 1024]; + + while (true) + { + var numRead = tarIn.Read(rdbuf, 0, rdbuf.Length); + + if (numRead <= 0) + { + break; + } + + for (int off = 0, b = 0; b < numRead; ++b) + { + if (rdbuf[b] == 10) + { + var s = Encoding.ASCII.GetString(rdbuf, off, b - off); + outw.WriteLine(s); + off = b + 1; + } + } + } + } + else + { + // No translation required. + tarIn.CopyEntryContents(outputStream); + } + } + + /// + /// Write an entry to the archive. This method will call the putNextEntry + /// and then write the contents of the entry, and finally call closeEntry() + /// for entries that are files. For directories, it will call putNextEntry(), + /// and then, if the recurse flag is true, process each entry that is a + /// child of the directory. + /// + /// + /// The TarEntry representing the entry to write to the archive. + /// + /// + /// If true, process the children of directory entries. + /// + public void WriteEntry(TarEntry sourceEntry, bool recurse) + { + if (sourceEntry == null) + { + throw new ArgumentNullException(nameof(sourceEntry)); + } + + if (isDisposed) + { + throw new ObjectDisposedException("TarArchive"); + } + + try + { + if (recurse) + { + TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName, + sourceEntry.GroupId, sourceEntry.GroupName); + } + WriteEntryCore(sourceEntry, recurse); + } + finally + { + if (recurse) + { + TarHeader.RestoreSetValues(); + } + } + } + + /// + /// Write an entry to the archive. This method will call the putNextEntry + /// and then write the contents of the entry, and finally call closeEntry() + /// for entries that are files. For directories, it will call putNextEntry(), + /// and then, if the recurse flag is true, process each entry that is a + /// child of the directory. + /// + /// + /// The TarEntry representing the entry to write to the archive. + /// + /// + /// If true, process the children of directory entries. + /// + private void WriteEntryCore(TarEntry sourceEntry, bool recurse) + { + string tempFileName = null; + var entryFilename = sourceEntry.File; + + var entry = (TarEntry)sourceEntry.Clone(); + + if (applyUserInfoOverrides) + { + entry.GroupId = groupId; + entry.GroupName = groupName; + entry.UserId = userId; + entry.UserName = userName; + } + + OnProgressMessageEvent(entry, null); + + if (asciiTranslate && !entry.IsDirectory) + { + if (!IsBinary(entryFilename)) + { + tempFileName = PathUtils.GetTempFileName(); + + using (var inStream = File.OpenText(entryFilename)) + { + using Stream outStream = File.Create(tempFileName); + while (true) + { + var line = inStream.ReadLine(); + if (line == null) + { + break; + } + var data = Encoding.ASCII.GetBytes(line); + outStream.Write(data, 0, data.Length); + outStream.WriteByte((byte)'\n'); + } + + outStream.Flush(); + } + + entry.Size = new FileInfo(tempFileName).Length; + entryFilename = tempFileName; + } + } + + string newName = null; + + if (!string.IsNullOrEmpty(rootPath)) + { + if (entry.Name.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) + { + newName = entry.Name[(rootPath.Length + 1)..]; + } + } + + if (pathPrefix != null) + { + newName = (newName == null) ? pathPrefix + "/" + entry.Name : pathPrefix + "/" + newName; + } + + if (newName != null) + { + entry.Name = newName; + } + + tarOut.PutNextEntry(entry); + + if (entry.IsDirectory) + { + if (recurse) + { + var list = entry.GetDirectoryEntries(); + for (var i = 0; i < list.Length; ++i) + { + WriteEntryCore(list[i], recurse); + } + } + } + else + { + using (Stream inputStream = File.OpenRead(entryFilename)) + { + var localBuffer = new byte[32 * 1024]; + while (true) + { + var numRead = inputStream.Read(localBuffer, 0, localBuffer.Length); + + if (numRead <= 0) + { + break; + } + + tarOut.Write(localBuffer, 0, numRead); + } + } + + if (!string.IsNullOrEmpty(tempFileName)) + { + File.Delete(tempFileName); + } + + tarOut.CloseEntry(); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the unmanaged resources used by the FileStream and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; + /// false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + isDisposed = true; + if (disposing) + { + if (tarOut != null) + { + tarOut.Flush(); + tarOut.Dispose(); + } + + tarIn?.Dispose(); + } + } + } + + /// + /// Closes the archive and releases any associated resources. + /// + public virtual void Close() + { + Dispose(true); + } + + /// + /// Ensures that resources are freed and other cleanup operations are performed + /// when the garbage collector reclaims the . + /// + ~TarArchive() + { + Dispose(false); + } + + private static void EnsureDirectoryExists(string directoryName) + { + if (!Directory.Exists(directoryName)) + { + try + { + Directory.CreateDirectory(directoryName); + } + catch (Exception e) + { + throw new TarException("Exception creating directory '" + directoryName + "', " + e.Message, e); + } + } + } + + // TODO: TarArchive - Is there a better way to test for a text file? + // It no longer reads entire files into memory but is still a weak test! + // This assumes that byte values 0-7, 14-31 or 255 are binary + // and that all non text files contain one of these values + private static bool IsBinary(string filename) + { + using var fs = File.OpenRead(filename); + var sampleSize = Math.Min(4096, (int)fs.Length); + var content = new byte[sampleSize]; + + var bytesRead = fs.Read(content, 0, sampleSize); + + for (var i = 0; i < bytesRead; ++i) + { + var b = content[i]; + if (b is < 8 or > 13 and < 32 or 255) + { + return true; + } + } + return false; + } + + #region Instance Fields + + private bool keepOldFiles; + private bool asciiTranslate; + + private int userId; + private string userName = string.Empty; + private int groupId; + private string groupName = string.Empty; + + private string rootPath; + private string pathPrefix; + + private bool applyUserInfoOverrides; + + private readonly TarInputStream tarIn; + private readonly TarOutputStream tarOut; + private bool isDisposed; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs index fd0ccc88a..3073d9408 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs @@ -1,599 +1,596 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// The TarBuffer class implements the tar archive concept +/// of a buffered input stream. This concept goes back to the +/// days of blocked tape drives and special io devices. In the +/// C# universe, the only real function that this class +/// performs is to ensure that files have the correct "record" +/// size, or other tars will complain. +///

+/// You should never have a need to access this class directly. +/// TarBuffers are created by Tar IO Streams. +///

+///
+public class TarBuffer { - /// - /// The TarBuffer class implements the tar archive concept - /// of a buffered input stream. This concept goes back to the - /// days of blocked tape drives and special io devices. In the - /// C# universe, the only real function that this class - /// performs is to ensure that files have the correct "record" - /// size, or other tars will complain. - ///

- /// You should never have a need to access this class directly. - /// TarBuffers are created by Tar IO Streams. - ///

- ///
- public class TarBuffer - { - /* A quote from GNU tar man file on blocking and records - A `tar' archive file contains a series of blocks. Each block - contains `BLOCKSIZE' bytes. Although this format may be thought of as - being on magnetic tape, other media are often used. - - Each file archived is represented by a header block which describes - the file, followed by zero or more blocks which give the contents of - the file. At the end of the archive file there may be a block filled - with binary zeros as an end-of-file marker. A reasonable system should - write a block of zeros at the end, but must not assume that such a - block exists when reading an archive. - - The blocks may be "blocked" for physical I/O operations. Each - record of N blocks is written with a single 'write ()' - operation. On magnetic tapes, the result of such a write is a single - record. When writing an archive, the last record of blocks should be - written at the full size, with blocks after the zero block containing - all zeros. When reading an archive, a reasonable system should - properly handle an archive whose last record is shorter than the rest, - or which contains garbage records after a zero block. - */ - - #region Constants - - /// - /// The size of a block in a tar archive in bytes. - /// - /// This is 512 bytes. - public const int BlockSize = 512; - - /// - /// The number of blocks in a default record. - /// - /// - /// The default value is 20 blocks per record. - /// - public const int DefaultBlockFactor = 20; - - /// - /// The size in bytes of a default record. - /// - /// - /// The default size is 10KB. - /// - public const int DefaultRecordSize = BlockSize * DefaultBlockFactor; - - #endregion Constants - - /// - /// Get the record size for this buffer - /// - /// The record size in bytes. - /// This is equal to the multiplied by the - public int RecordSize - { - get - { - return recordSize; - } - } - - /// - /// Get the TAR Buffer's record size. - /// - /// The record size in bytes. - /// This is equal to the multiplied by the - [Obsolete("Use RecordSize property instead")] - public int GetRecordSize() - { - return recordSize; - } - - /// - /// Get the Blocking factor for the buffer - /// - /// This is the number of blocks in each record. - public int BlockFactor - { - get - { - return blockFactor; - } - } - - /// - /// Get the TAR Buffer's block factor - /// - /// The block factor; the number of blocks per record. - [Obsolete("Use BlockFactor property instead")] - public int GetBlockFactor() - { - return blockFactor; - } - - /// - /// Construct a default TarBuffer - /// - protected TarBuffer() - { - } - - /// - /// Create TarBuffer for reading with default BlockFactor - /// - /// Stream to buffer - /// A new suitable for input. - public static TarBuffer CreateInputTarBuffer(Stream inputStream) - { - if (inputStream == null) - { - throw new ArgumentNullException(nameof(inputStream)); - } - - return CreateInputTarBuffer(inputStream, DefaultBlockFactor); - } - - /// - /// Construct TarBuffer for reading inputStream setting BlockFactor - /// - /// Stream to buffer - /// Blocking factor to apply - /// A new suitable for input. - public static TarBuffer CreateInputTarBuffer(Stream inputStream, int blockFactor) - { - if (inputStream == null) - { - throw new ArgumentNullException(nameof(inputStream)); - } - - if (blockFactor <= 0) - { - throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative"); - } - - var tarBuffer = new TarBuffer(); - tarBuffer.inputStream = inputStream; - tarBuffer.outputStream = null; - tarBuffer.Initialize(blockFactor); - - return tarBuffer; - } - - /// - /// Construct TarBuffer for writing with default BlockFactor - /// - /// output stream for buffer - /// A new suitable for output. - public static TarBuffer CreateOutputTarBuffer(Stream outputStream) - { - if (outputStream == null) - { - throw new ArgumentNullException(nameof(outputStream)); - } - - return CreateOutputTarBuffer(outputStream, DefaultBlockFactor); - } - - /// - /// Construct TarBuffer for writing Tar output to streams. - /// - /// Output stream to write to. - /// Blocking factor to apply - /// A new suitable for output. - public static TarBuffer CreateOutputTarBuffer(Stream outputStream, int blockFactor) - { - if (outputStream == null) - { - throw new ArgumentNullException(nameof(outputStream)); - } - - if (blockFactor <= 0) - { - throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative"); - } - - var tarBuffer = new TarBuffer(); - tarBuffer.inputStream = null; - tarBuffer.outputStream = outputStream; - tarBuffer.Initialize(blockFactor); - - return tarBuffer; - } - - /// - /// Initialization common to all constructors. - /// - private void Initialize(int archiveBlockFactor) - { - blockFactor = archiveBlockFactor; - recordSize = archiveBlockFactor * BlockSize; - recordBuffer = new byte[RecordSize]; - - if (inputStream != null) - { - currentRecordIndex = -1; - currentBlockIndex = BlockFactor; - } - else - { - currentRecordIndex = 0; - currentBlockIndex = 0; - } - } - - /// - /// Determine if an archive block indicates End of Archive. End of - /// archive is indicated by a block that consists entirely of null bytes. - /// All remaining blocks for the record should also be null's - /// However some older tars only do a couple of null blocks (Old GNU tar for one) - /// and also partial records - /// - /// The data block to check. - /// Returns true if the block is an EOF block; false otherwise. - [Obsolete("Use IsEndOfArchiveBlock instead")] - public bool IsEOFBlock(byte[] block) - { - if (block == null) - { - throw new ArgumentNullException(nameof(block)); - } - - if (block.Length != BlockSize) - { - throw new ArgumentException("block length is invalid"); - } - - for (int i = 0; i < BlockSize; ++i) - { - if (block[i] != 0) - { - return false; - } - } - - return true; - } - - /// - /// Determine if an archive block indicates the End of an Archive has been reached. - /// End of archive is indicated by a block that consists entirely of null bytes. - /// All remaining blocks for the record should also be null's - /// However some older tars only do a couple of null blocks (Old GNU tar for one) - /// and also partial records - /// - /// The data block to check. - /// Returns true if the block is an EOF block; false otherwise. - public static bool IsEndOfArchiveBlock(byte[] block) - { - if (block == null) - { - throw new ArgumentNullException(nameof(block)); - } - - if (block.Length != BlockSize) - { - throw new ArgumentException("block length is invalid"); - } - - for (int i = 0; i < BlockSize; ++i) - { - if (block[i] != 0) - { - return false; - } - } - - return true; - } - - /// - /// Skip over a block on the input stream. - /// - public void SkipBlock() - { - if (inputStream == null) - { - throw new TarException("no input stream defined"); - } - - if (currentBlockIndex >= BlockFactor) - { - if (!ReadRecord()) - { - throw new TarException("Failed to read a record"); - } - } - - currentBlockIndex++; - } - - /// - /// Read a block from the input stream. - /// - /// - /// The block of data read. - /// - public byte[] ReadBlock() - { - if (inputStream == null) - { - throw new TarException("TarBuffer.ReadBlock - no input stream defined"); - } - - if (currentBlockIndex >= BlockFactor) - { - if (!ReadRecord()) - { - throw new TarException("Failed to read a record"); - } - } - - byte[] result = new byte[BlockSize]; - - Array.Copy(recordBuffer, (currentBlockIndex * BlockSize), result, 0, BlockSize); - currentBlockIndex++; - return result; - } - - /// - /// Read a record from data stream. - /// - /// - /// false if End-Of-File, else true. - /// - private bool ReadRecord() - { - if (inputStream == null) - { - throw new TarException("no input stream defined"); - } - - currentBlockIndex = 0; - - int offset = 0; - int bytesNeeded = RecordSize; - - while (bytesNeeded > 0) - { - long numBytes = inputStream.Read(recordBuffer, offset, bytesNeeded); - - // - // NOTE - // We have found EOF, and the record is not full! - // - // This is a broken archive. It does not follow the standard - // blocking algorithm. However, because we are generous, and - // it requires little effort, we will simply ignore the error - // and continue as if the entire record were read. This does - // not appear to break anything upstream. We used to return - // false in this case. - // - // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. - // - if (numBytes <= 0) - { - break; - } - - offset += (int)numBytes; - bytesNeeded -= (int)numBytes; - } - - currentRecordIndex++; - return true; - } - - /// - /// Get the current block number, within the current record, zero based. - /// - /// Block numbers are zero based values - /// - public int CurrentBlock - { - get { return currentBlockIndex; } - } - - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner { get; set; } = true; - - /// - /// Get the current block number, within the current record, zero based. - /// - /// - /// The current zero based block number. - /// - /// - /// The absolute block number = (record number * block factor) + block number. - /// - [Obsolete("Use CurrentBlock property instead")] - public int GetCurrentBlockNum() - { - return currentBlockIndex; - } - - /// - /// Get the current record number. - /// - /// - /// The current zero based record number. - /// - public int CurrentRecord - { - get { return currentRecordIndex; } - } - - /// - /// Get the current record number. - /// - /// - /// The current zero based record number. - /// - [Obsolete("Use CurrentRecord property instead")] - public int GetCurrentRecordNum() - { - return currentRecordIndex; - } - - /// - /// Write a block of data to the archive. - /// - /// - /// The data to write to the archive. - /// - public void WriteBlock(byte[] block) - { - if (block == null) - { - throw new ArgumentNullException(nameof(block)); - } - - if (outputStream == null) - { - throw new TarException("TarBuffer.WriteBlock - no output stream defined"); - } - - if (block.Length != BlockSize) - { - string errorText = string.Format("TarBuffer.WriteBlock - block to write has length '{0}' which is not the block size of '{1}'", - block.Length, BlockSize); - throw new TarException(errorText); - } - - if (currentBlockIndex >= BlockFactor) - { - WriteRecord(); - } - - Array.Copy(block, 0, recordBuffer, (currentBlockIndex * BlockSize), BlockSize); - currentBlockIndex++; - } - - /// - /// Write an archive record to the archive, where the record may be - /// inside of a larger array buffer. The buffer must be "offset plus - /// record size" long. - /// - /// - /// The buffer containing the record data to write. - /// - /// - /// The offset of the record data within buffer. - /// - public void WriteBlock(byte[] buffer, int offset) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (outputStream == null) - { - throw new TarException("TarBuffer.WriteBlock - no output stream defined"); - } - - if ((offset < 0) || (offset >= buffer.Length)) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if ((offset + BlockSize) > buffer.Length) - { - string errorText = string.Format("TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less than the record size of '{2}'", - buffer.Length, offset, recordSize); - throw new TarException(errorText); - } - - if (currentBlockIndex >= BlockFactor) - { - WriteRecord(); - } - - Array.Copy(buffer, offset, recordBuffer, (currentBlockIndex * BlockSize), BlockSize); - - currentBlockIndex++; - } - - /// - /// Write a TarBuffer record to the archive. - /// - private void WriteRecord() - { - if (outputStream == null) - { - throw new TarException("TarBuffer.WriteRecord no output stream defined"); - } - - outputStream.Write(recordBuffer, 0, RecordSize); - outputStream.Flush(); - - currentBlockIndex = 0; - currentRecordIndex++; - } - - /// - /// WriteFinalRecord writes the current record buffer to output any unwritten data is present. - /// - /// Any trailing bytes are set to zero which is by definition correct behaviour - /// for the end of a tar stream. - private void WriteFinalRecord() - { - if (outputStream == null) - { - throw new TarException("TarBuffer.WriteFinalRecord no output stream defined"); - } - - if (currentBlockIndex > 0) - { - int dataBytes = currentBlockIndex * BlockSize; - Array.Clear(recordBuffer, dataBytes, RecordSize - dataBytes); - WriteRecord(); - } - - outputStream.Flush(); - } - - /// - /// Close the TarBuffer. If this is an output buffer, also flush the - /// current block before closing. - /// - public void Close() - { - if (outputStream != null) - { - WriteFinalRecord(); - - if (IsStreamOwner) - { - outputStream.Dispose(); - } - outputStream = null; - } - else if (inputStream != null) - { - if (IsStreamOwner) - { - inputStream.Dispose(); - } - inputStream = null; - } - } - - #region Instance Fields - - private Stream inputStream; - private Stream outputStream; - - private byte[] recordBuffer; - private int currentBlockIndex; - private int currentRecordIndex; - - private int recordSize = DefaultRecordSize; - private int blockFactor = DefaultBlockFactor; - - #endregion Instance Fields - } + /* A quote from GNU tar man file on blocking and records + A `tar' archive file contains a series of blocks. Each block + contains `BLOCKSIZE' bytes. Although this format may be thought of as + being on magnetic tape, other media are often used. + + Each file archived is represented by a header block which describes + the file, followed by zero or more blocks which give the contents of + the file. At the end of the archive file there may be a block filled + with binary zeros as an end-of-file marker. A reasonable system should + write a block of zeros at the end, but must not assume that such a + block exists when reading an archive. + + The blocks may be "blocked" for physical I/O operations. Each + record of N blocks is written with a single 'write ()' + operation. On magnetic tapes, the result of such a write is a single + record. When writing an archive, the last record of blocks should be + written at the full size, with blocks after the zero block containing + all zeros. When reading an archive, a reasonable system should + properly handle an archive whose last record is shorter than the rest, + or which contains garbage records after a zero block. + */ + + #region Constants + + /// + /// The size of a block in a tar archive in bytes. + /// + /// This is 512 bytes. + public const int BlockSize = 512; + + /// + /// The number of blocks in a default record. + /// + /// + /// The default value is 20 blocks per record. + /// + public const int DefaultBlockFactor = 20; + + /// + /// The size in bytes of a default record. + /// + /// + /// The default size is 10KB. + /// + public const int DefaultRecordSize = BlockSize * DefaultBlockFactor; + + #endregion Constants + + /// + /// Get the record size for this buffer + /// + /// The record size in bytes. + /// This is equal to the multiplied by the + public int RecordSize + { + get + { + return recordSize; + } + } + + /// + /// Get the TAR Buffer's record size. + /// + /// The record size in bytes. + /// This is equal to the multiplied by the + [Obsolete("Use RecordSize property instead")] + public int GetRecordSize() + { + return recordSize; + } + + /// + /// Get the Blocking factor for the buffer + /// + /// This is the number of blocks in each record. + public int BlockFactor + { + get + { + return blockFactor; + } + } + + /// + /// Get the TAR Buffer's block factor + /// + /// The block factor; the number of blocks per record. + [Obsolete("Use BlockFactor property instead")] + public int GetBlockFactor() + { + return blockFactor; + } + + /// + /// Construct a default TarBuffer + /// + protected TarBuffer() + { + } + + /// + /// Create TarBuffer for reading with default BlockFactor + /// + /// Stream to buffer + /// A new suitable for input. + public static TarBuffer CreateInputTarBuffer(Stream inputStream) + { + return inputStream == null + ? throw new ArgumentNullException(nameof(inputStream)) + : CreateInputTarBuffer(inputStream, DefaultBlockFactor); + } + + /// + /// Construct TarBuffer for reading inputStream setting BlockFactor + /// + /// Stream to buffer + /// Blocking factor to apply + /// A new suitable for input. + public static TarBuffer CreateInputTarBuffer(Stream inputStream, int blockFactor) + { + if (inputStream == null) + { + throw new ArgumentNullException(nameof(inputStream)); + } + + if (blockFactor <= 0) + { + throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative"); + } + + var tarBuffer = new TarBuffer + { + inputStream = inputStream, + outputStream = null + }; + tarBuffer.Initialize(blockFactor); + + return tarBuffer; + } + + /// + /// Construct TarBuffer for writing with default BlockFactor + /// + /// output stream for buffer + /// A new suitable for output. + public static TarBuffer CreateOutputTarBuffer(Stream outputStream) + { + return outputStream == null + ? throw new ArgumentNullException(nameof(outputStream)) + : CreateOutputTarBuffer(outputStream, DefaultBlockFactor); + } + + /// + /// Construct TarBuffer for writing Tar output to streams. + /// + /// Output stream to write to. + /// Blocking factor to apply + /// A new suitable for output. + public static TarBuffer CreateOutputTarBuffer(Stream outputStream, int blockFactor) + { + if (outputStream == null) + { + throw new ArgumentNullException(nameof(outputStream)); + } + + if (blockFactor <= 0) + { + throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative"); + } + + var tarBuffer = new TarBuffer + { + inputStream = null, + outputStream = outputStream + }; + tarBuffer.Initialize(blockFactor); + + return tarBuffer; + } + + /// + /// Initialization common to all constructors. + /// + private void Initialize(int archiveBlockFactor) + { + blockFactor = archiveBlockFactor; + recordSize = archiveBlockFactor * BlockSize; + recordBuffer = new byte[RecordSize]; + + if (inputStream != null) + { + currentRecordIndex = -1; + currentBlockIndex = BlockFactor; + } + else + { + currentRecordIndex = 0; + currentBlockIndex = 0; + } + } + + /// + /// Determine if an archive block indicates End of Archive. End of + /// archive is indicated by a block that consists entirely of null bytes. + /// All remaining blocks for the record should also be null's + /// However some older tars only do a couple of null blocks (Old GNU tar for one) + /// and also partial records + /// + /// The data block to check. + /// Returns true if the block is an EOF block; false otherwise. + [Obsolete("Use IsEndOfArchiveBlock instead")] + public bool IsEOFBlock(byte[] block) + { + if (block == null) + { + throw new ArgumentNullException(nameof(block)); + } + + if (block.Length != BlockSize) + { + throw new ArgumentException("block length is invalid"); + } + + for (var i = 0; i < BlockSize; ++i) + { + if (block[i] != 0) + { + return false; + } + } + + return true; + } + + /// + /// Determine if an archive block indicates the End of an Archive has been reached. + /// End of archive is indicated by a block that consists entirely of null bytes. + /// All remaining blocks for the record should also be null's + /// However some older tars only do a couple of null blocks (Old GNU tar for one) + /// and also partial records + /// + /// The data block to check. + /// Returns true if the block is an EOF block; false otherwise. + public static bool IsEndOfArchiveBlock(byte[] block) + { + if (block == null) + { + throw new ArgumentNullException(nameof(block)); + } + + if (block.Length != BlockSize) + { + throw new ArgumentException("block length is invalid"); + } + + for (var i = 0; i < BlockSize; ++i) + { + if (block[i] != 0) + { + return false; + } + } + + return true; + } + + /// + /// Skip over a block on the input stream. + /// + public void SkipBlock() + { + if (inputStream == null) + { + throw new TarException("no input stream defined"); + } + + if (currentBlockIndex >= BlockFactor) + { + if (!ReadRecord()) + { + throw new TarException("Failed to read a record"); + } + } + + currentBlockIndex++; + } + + /// + /// Read a block from the input stream. + /// + /// + /// The block of data read. + /// + public byte[] ReadBlock() + { + if (inputStream == null) + { + throw new TarException("TarBuffer.ReadBlock - no input stream defined"); + } + + if (currentBlockIndex >= BlockFactor) + { + if (!ReadRecord()) + { + throw new TarException("Failed to read a record"); + } + } + + var result = new byte[BlockSize]; + + Array.Copy(recordBuffer, currentBlockIndex * BlockSize, result, 0, BlockSize); + currentBlockIndex++; + return result; + } + + /// + /// Read a record from data stream. + /// + /// + /// false if End-Of-File, else true. + /// + private bool ReadRecord() + { + if (inputStream == null) + { + throw new TarException("no input stream defined"); + } + + currentBlockIndex = 0; + + var offset = 0; + var bytesNeeded = RecordSize; + + while (bytesNeeded > 0) + { + long numBytes = inputStream.Read(recordBuffer, offset, bytesNeeded); + + // + // NOTE + // We have found EOF, and the record is not full! + // + // This is a broken archive. It does not follow the standard + // blocking algorithm. However, because we are generous, and + // it requires little effort, we will simply ignore the error + // and continue as if the entire record were read. This does + // not appear to break anything upstream. We used to return + // false in this case. + // + // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. + // + if (numBytes <= 0) + { + break; + } + + offset += (int)numBytes; + bytesNeeded -= (int)numBytes; + } + + currentRecordIndex++; + return true; + } + + /// + /// Get the current block number, within the current record, zero based. + /// + /// Block numbers are zero based values + /// + public int CurrentBlock + { + get { return currentBlockIndex; } + } + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Get the current block number, within the current record, zero based. + /// + /// + /// The current zero based block number. + /// + /// + /// The absolute block number = (record number * block factor) + block number. + /// + [Obsolete("Use CurrentBlock property instead")] + public int GetCurrentBlockNum() + { + return currentBlockIndex; + } + + /// + /// Get the current record number. + /// + /// + /// The current zero based record number. + /// + public int CurrentRecord + { + get { return currentRecordIndex; } + } + + /// + /// Get the current record number. + /// + /// + /// The current zero based record number. + /// + [Obsolete("Use CurrentRecord property instead")] + public int GetCurrentRecordNum() + { + return currentRecordIndex; + } + + /// + /// Write a block of data to the archive. + /// + /// + /// The data to write to the archive. + /// + public void WriteBlock(byte[] block) + { + if (block == null) + { + throw new ArgumentNullException(nameof(block)); + } + + if (outputStream == null) + { + throw new TarException("TarBuffer.WriteBlock - no output stream defined"); + } + + if (block.Length != BlockSize) + { + var errorText = string.Format("TarBuffer.WriteBlock - block to write has length '{0}' which is not the block size of '{1}'", + block.Length, BlockSize); + throw new TarException(errorText); + } + + if (currentBlockIndex >= BlockFactor) + { + WriteRecord(); + } + + Array.Copy(block, 0, recordBuffer, currentBlockIndex * BlockSize, BlockSize); + currentBlockIndex++; + } + + /// + /// Write an archive record to the archive, where the record may be + /// inside of a larger array buffer. The buffer must be "offset plus + /// record size" long. + /// + /// + /// The buffer containing the record data to write. + /// + /// + /// The offset of the record data within buffer. + /// + public void WriteBlock(byte[] buffer, int offset) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (outputStream == null) + { + throw new TarException("TarBuffer.WriteBlock - no output stream defined"); + } + + if ((offset < 0) || (offset >= buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if ((offset + BlockSize) > buffer.Length) + { + var errorText = string.Format("TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less than the record size of '{2}'", + buffer.Length, offset, recordSize); + throw new TarException(errorText); + } + + if (currentBlockIndex >= BlockFactor) + { + WriteRecord(); + } + + Array.Copy(buffer, offset, recordBuffer, currentBlockIndex * BlockSize, BlockSize); + + currentBlockIndex++; + } + + /// + /// Write a TarBuffer record to the archive. + /// + private void WriteRecord() + { + if (outputStream == null) + { + throw new TarException("TarBuffer.WriteRecord no output stream defined"); + } + + outputStream.Write(recordBuffer, 0, RecordSize); + outputStream.Flush(); + + currentBlockIndex = 0; + currentRecordIndex++; + } + + /// + /// WriteFinalRecord writes the current record buffer to output any unwritten data is present. + /// + /// Any trailing bytes are set to zero which is by definition correct behaviour + /// for the end of a tar stream. + private void WriteFinalRecord() + { + if (outputStream == null) + { + throw new TarException("TarBuffer.WriteFinalRecord no output stream defined"); + } + + if (currentBlockIndex > 0) + { + var dataBytes = currentBlockIndex * BlockSize; + Array.Clear(recordBuffer, dataBytes, RecordSize - dataBytes); + WriteRecord(); + } + + outputStream.Flush(); + } + + /// + /// Close the TarBuffer. If this is an output buffer, also flush the + /// current block before closing. + /// + public void Close() + { + if (outputStream != null) + { + WriteFinalRecord(); + + if (IsStreamOwner) + { + outputStream.Dispose(); + } + outputStream = null; + } + else if (inputStream != null) + { + if (IsStreamOwner) + { + inputStream.Dispose(); + } + inputStream = null; + } + } + + #region Instance Fields + + private Stream inputStream; + private Stream outputStream; + + private byte[] recordBuffer; + private int currentBlockIndex; + private int currentRecordIndex; + + private int recordSize = DefaultRecordSize; + private int blockFactor = DefaultBlockFactor; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs index 2d4362c00..2ca88311f 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs @@ -1,598 +1,583 @@ +using MelonLoader.ICSharpCode.SharpZipLib.Core; using System; using System.IO; using System.Text; -using MelonLoader.ICSharpCode.SharpZipLib.Core; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// This class represents an entry in a Tar archive. It consists +/// of the entry's header, as well as the entry's File. Entries +/// can be instantiated in one of three ways, depending on how +/// they are to be used. +///

+/// TarEntries that are created from the header bytes read from +/// an archive are instantiated with the TarEntry( byte[] ) +/// constructor. These entries will be used when extracting from +/// or listing the contents of an archive. These entries have their +/// header filled in using the header bytes. They also set the File +/// to null, since they reference an archive entry not a file.

+///

+/// TarEntries that are created from files that are to be written +/// into an archive are instantiated with the CreateEntryFromFile(string) +/// pseudo constructor. These entries have their header filled in using +/// the File's information. They also keep a reference to the File +/// for convenience when writing entries.

+///

+/// Finally, TarEntries can be constructed from nothing but a name. +/// This allows the programmer to construct the entry by hand, for +/// instance when only an InputStream is available for writing to +/// the archive, and the header information is constructed from +/// other information. In this case the header fields are set to +/// defaults and the File is set to null.

+/// +///
+public class TarEntry { - /// - /// This class represents an entry in a Tar archive. It consists - /// of the entry's header, as well as the entry's File. Entries - /// can be instantiated in one of three ways, depending on how - /// they are to be used. - ///

- /// TarEntries that are created from the header bytes read from - /// an archive are instantiated with the TarEntry( byte[] ) - /// constructor. These entries will be used when extracting from - /// or listing the contents of an archive. These entries have their - /// header filled in using the header bytes. They also set the File - /// to null, since they reference an archive entry not a file.

- ///

- /// TarEntries that are created from files that are to be written - /// into an archive are instantiated with the CreateEntryFromFile(string) - /// pseudo constructor. These entries have their header filled in using - /// the File's information. They also keep a reference to the File - /// for convenience when writing entries.

- ///

- /// Finally, TarEntries can be constructed from nothing but a name. - /// This allows the programmer to construct the entry by hand, for - /// instance when only an InputStream is available for writing to - /// the archive, and the header information is constructed from - /// other information. In this case the header fields are set to - /// defaults and the File is set to null.

- /// - ///
- public class TarEntry - { - #region Constructors - - /// - /// Initialise a default instance of . - /// - private TarEntry() - { - header = new TarHeader(); - } - - /// - /// Construct an entry from an archive's header bytes. File is set - /// to null. - /// - /// - /// The header bytes from a tar archive entry. - /// - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public TarEntry(byte[] headerBuffer) : this(headerBuffer, null) - { - } - - /// - /// Construct an entry from an archive's header bytes. File is set - /// to null. - /// - /// - /// The header bytes from a tar archive entry. - /// - /// - /// The used for the Name fields, or null for ASCII only - /// - public TarEntry(byte[] headerBuffer, Encoding nameEncoding) - { - header = new TarHeader(); - header.ParseBuffer(headerBuffer, nameEncoding); - } - - /// - /// Construct a TarEntry using the header provided - /// - /// Header details for entry - public TarEntry(TarHeader header) - { - if (header == null) - { - throw new ArgumentNullException(nameof(header)); - } - - this.header = (TarHeader)header.Clone(); - } - - #endregion Constructors - - #region ICloneable Members - - /// - /// Clone this tar entry. - /// - /// Returns a clone of this entry. - public object Clone() - { - var entry = new TarEntry(); - entry.file = file; - entry.header = (TarHeader)header.Clone(); - entry.Name = Name; - return entry; - } - - #endregion ICloneable Members - - /// - /// Construct an entry with only a name. - /// This allows the programmer to construct the entry's header "by hand". - /// - /// The name to use for the entry - /// Returns the newly created - public static TarEntry CreateTarEntry(string name) - { - var entry = new TarEntry(); - TarEntry.NameTarHeader(entry.header, name); - return entry; - } - - /// - /// Construct an entry for a file. File is set to file, and the - /// header is constructed from information from the file. - /// - /// The file name that the entry represents. - /// Returns the newly created - public static TarEntry CreateEntryFromFile(string fileName) - { - var entry = new TarEntry(); - entry.GetFileTarHeader(entry.header, fileName); - return entry; - } - - /// - /// Determine if the two entries are equal. Equality is determined - /// by the header names being equal. - /// - /// The to compare with the current Object. - /// - /// True if the entries are equal; false if not. - /// - public override bool Equals(object obj) - { - var localEntry = obj as TarEntry; - - if (localEntry != null) - { - return Name.Equals(localEntry.Name); - } - return false; - } - - /// - /// Derive a Hash value for the current - /// - /// A Hash code for the current - public override int GetHashCode() - { - return Name.GetHashCode(); - } - - /// - /// Determine if the given entry is a descendant of this entry. - /// Descendancy is determined by the name of the descendant - /// starting with this entry's name. - /// - /// - /// Entry to be checked as a descendent of this. - /// - /// - /// True if entry is a descendant of this. - /// - public bool IsDescendent(TarEntry toTest) - { - if (toTest == null) - { - throw new ArgumentNullException(nameof(toTest)); - } - - return toTest.Name.StartsWith(Name, StringComparison.Ordinal); - } - - /// - /// Get this entry's header. - /// - /// - /// This entry's TarHeader. - /// - public TarHeader TarHeader - { - get - { - return header; - } - } - - /// - /// Get/Set this entry's name. - /// - public string Name - { - get - { - return header.Name; - } - set - { - header.Name = value; - } - } - - /// - /// Get/set this entry's user id. - /// - public int UserId - { - get - { - return header.UserId; - } - set - { - header.UserId = value; - } - } - - /// - /// Get/set this entry's group id. - /// - public int GroupId - { - get - { - return header.GroupId; - } - set - { - header.GroupId = value; - } - } - - /// - /// Get/set this entry's user name. - /// - public string UserName - { - get - { - return header.UserName; - } - set - { - header.UserName = value; - } - } - - /// - /// Get/set this entry's group name. - /// - public string GroupName - { - get - { - return header.GroupName; - } - set - { - header.GroupName = value; - } - } - - /// - /// Convenience method to set this entry's group and user ids. - /// - /// - /// This entry's new user id. - /// - /// - /// This entry's new group id. - /// - public void SetIds(int userId, int groupId) - { - UserId = userId; - GroupId = groupId; - } - - /// - /// Convenience method to set this entry's group and user names. - /// - /// - /// This entry's new user name. - /// - /// - /// This entry's new group name. - /// - public void SetNames(string userName, string groupName) - { - UserName = userName; - GroupName = groupName; - } - - /// - /// Get/Set the modification time for this entry - /// - public DateTime ModTime - { - get - { - return header.ModTime; - } - set - { - header.ModTime = value; - } - } - - /// - /// Get this entry's file. - /// - /// - /// This entry's file. - /// - public string File - { - get - { - return file; - } - } - - /// - /// Get/set this entry's recorded file size. - /// - public long Size - { - get - { - return header.Size; - } - set - { - header.Size = value; - } - } - - /// - /// Return true if this entry represents a directory, false otherwise - /// - /// - /// True if this entry is a directory. - /// - public bool IsDirectory - { - get - { - if (file != null) - { - return Directory.Exists(file); - } - - if (header != null) - { - if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal)) - { - return true; - } - } - return false; - } - } - - /// - /// Fill in a TarHeader with information from a File. - /// - /// - /// The TarHeader to fill in. - /// - /// - /// The file from which to get the header information. - /// - public void GetFileTarHeader(TarHeader header, string file) - { - if (header == null) - { - throw new ArgumentNullException(nameof(header)); - } - - if (file == null) - { - throw new ArgumentNullException(nameof(file)); - } - - this.file = file; - - // bugfix from torhovl from #D forum: - string name = file; - - // 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory - if (name.IndexOf(Directory.GetCurrentDirectory(), StringComparison.Ordinal) == 0) - { - name = name.Substring(Directory.GetCurrentDirectory().Length); - } - - /* - if (Path.DirectorySeparatorChar == '\\') - { - // check if the OS is Windows - // Strip off drive letters! - if (name.Length > 2) - { - char ch1 = name[0]; - char ch2 = name[1]; - - if (ch2 == ':' && Char.IsLetter(ch1)) - { - name = name.Substring(2); - } - } - } - */ - - name = name.Replace(Path.DirectorySeparatorChar, '/'); - - // No absolute pathnames - // Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\", - // so we loop on starting /'s. - while (name.StartsWith("/", StringComparison.Ordinal)) - { - name = name.Substring(1); - } - - header.LinkName = String.Empty; - header.Name = name; - - if (Directory.Exists(file)) - { - header.Mode = 1003; // Magic number for security access for a UNIX filesystem - header.TypeFlag = TarHeader.LF_DIR; - if ((header.Name.Length == 0) || header.Name[header.Name.Length - 1] != '/') - { - header.Name = header.Name + "/"; - } - - header.Size = 0; - } - else - { - header.Mode = 33216; // Magic number for security access for a UNIX filesystem - header.TypeFlag = TarHeader.LF_NORMAL; - header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length; - } - - header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime(); - header.DevMajor = 0; - header.DevMinor = 0; - } - - /// - /// Get entries for all files present in this entries directory. - /// If this entry doesnt represent a directory zero entries are returned. - /// - /// - /// An array of TarEntry's for this entry's children. - /// - public TarEntry[] GetDirectoryEntries() - { - if ((file == null) || !Directory.Exists(file)) - { - return Empty.Array(); - } - - string[] list = Directory.GetFileSystemEntries(file); - TarEntry[] result = new TarEntry[list.Length]; - - for (int i = 0; i < list.Length; ++i) - { - result[i] = TarEntry.CreateEntryFromFile(list[i]); - } - - return result; - } - - /// - /// Write an entry's header information to a header buffer. - /// - /// - /// The tar entry header buffer to fill in. - /// - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public void WriteEntryHeader(byte[] outBuffer) - { - WriteEntryHeader(outBuffer, null); - } - - /// - /// Write an entry's header information to a header buffer. - /// - /// - /// The tar entry header buffer to fill in. - /// - /// - /// The used for the Name fields, or null for ASCII only - /// - public void WriteEntryHeader(byte[] outBuffer, Encoding nameEncoding) - { - header.WriteHeader(outBuffer, nameEncoding); - } - - /// - /// Convenience method that will modify an entry's name directly - /// in place in an entry header buffer byte array. - /// - /// - /// The buffer containing the entry header to modify. - /// - /// - /// The new name to place into the header buffer. - /// - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - static public void AdjustEntryName(byte[] buffer, string newName) - { - AdjustEntryName(buffer, newName, null); - } - - /// - /// Convenience method that will modify an entry's name directly - /// in place in an entry header buffer byte array. - /// - /// - /// The buffer containing the entry header to modify. - /// - /// - /// The new name to place into the header buffer. - /// - /// - /// The used for the Name fields, or null for ASCII only - /// - static public void AdjustEntryName(byte[] buffer, string newName, Encoding nameEncoding) - { - TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN, nameEncoding); - } - - /// - /// Fill in a TarHeader given only the entry's name. - /// - /// - /// The TarHeader to fill in. - /// - /// - /// The tar entry name. - /// - static public void NameTarHeader(TarHeader header, string name) - { - if (header == null) - { - throw new ArgumentNullException(nameof(header)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - bool isDir = name.EndsWith("/", StringComparison.Ordinal); - - header.Name = name; - header.Mode = isDir ? 1003 : 33216; - header.UserId = 0; - header.GroupId = 0; - header.Size = 0; - - header.ModTime = DateTime.UtcNow; - - header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL; - - header.LinkName = String.Empty; - header.UserName = String.Empty; - header.GroupName = String.Empty; - - header.DevMajor = 0; - header.DevMinor = 0; - } - - #region Instance Fields - - /// - /// The name of the file this entry represents or null if the entry is not based on a file. - /// - private string file; - - /// - /// The entry's header information. - /// - private TarHeader header; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Initialise a default instance of . + /// + private TarEntry() + { + header = new TarHeader(); + } + + /// + /// Construct an entry from an archive's header bytes. File is set + /// to null. + /// + /// + /// The header bytes from a tar archive entry. + /// + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public TarEntry(byte[] headerBuffer) : this(headerBuffer, null) + { + } + + /// + /// Construct an entry from an archive's header bytes. File is set + /// to null. + /// + /// + /// The header bytes from a tar archive entry. + /// + /// + /// The used for the Name fields, or null for ASCII only + /// + public TarEntry(byte[] headerBuffer, Encoding nameEncoding) + { + header = new TarHeader(); + header.ParseBuffer(headerBuffer, nameEncoding); + } + + /// + /// Construct a TarEntry using the header provided + /// + /// Header details for entry + public TarEntry(TarHeader header) + { + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } + + this.header = (TarHeader)header.Clone(); + } + + #endregion Constructors + + #region ICloneable Members + + /// + /// Clone this tar entry. + /// + /// Returns a clone of this entry. + public object Clone() + { + var entry = new TarEntry + { + file = file, + header = (TarHeader)header.Clone(), + Name = Name + }; + return entry; + } + + #endregion ICloneable Members + + /// + /// Construct an entry with only a name. + /// This allows the programmer to construct the entry's header "by hand". + /// + /// The name to use for the entry + /// Returns the newly created + public static TarEntry CreateTarEntry(string name) + { + var entry = new TarEntry(); + TarEntry.NameTarHeader(entry.header, name); + return entry; + } + + /// + /// Construct an entry for a file. File is set to file, and the + /// header is constructed from information from the file. + /// + /// The file name that the entry represents. + /// Returns the newly created + public static TarEntry CreateEntryFromFile(string fileName) + { + var entry = new TarEntry(); + entry.GetFileTarHeader(entry.header, fileName); + return entry; + } + + /// + /// Determine if the two entries are equal. Equality is determined + /// by the header names being equal. + /// + /// The to compare with the current Object. + /// + /// True if the entries are equal; false if not. + /// + public override bool Equals(object obj) + { + return obj is TarEntry localEntry && Name.Equals(localEntry.Name); + } + + /// + /// Derive a Hash value for the current + /// + /// A Hash code for the current + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + /// + /// Determine if the given entry is a descendant of this entry. + /// Descendancy is determined by the name of the descendant + /// starting with this entry's name. + /// + /// + /// Entry to be checked as a descendent of this. + /// + /// + /// True if entry is a descendant of this. + /// + public bool IsDescendent(TarEntry toTest) + { + return toTest == null ? throw new ArgumentNullException(nameof(toTest)) : toTest.Name.StartsWith(Name, StringComparison.Ordinal); + } + + /// + /// Get this entry's header. + /// + /// + /// This entry's TarHeader. + /// + public TarHeader TarHeader + { + get + { + return header; + } + } + + /// + /// Get/Set this entry's name. + /// + public string Name + { + get + { + return header.Name; + } + set + { + header.Name = value; + } + } + + /// + /// Get/set this entry's user id. + /// + public int UserId + { + get + { + return header.UserId; + } + set + { + header.UserId = value; + } + } + + /// + /// Get/set this entry's group id. + /// + public int GroupId + { + get + { + return header.GroupId; + } + set + { + header.GroupId = value; + } + } + + /// + /// Get/set this entry's user name. + /// + public string UserName + { + get + { + return header.UserName; + } + set + { + header.UserName = value; + } + } + + /// + /// Get/set this entry's group name. + /// + public string GroupName + { + get + { + return header.GroupName; + } + set + { + header.GroupName = value; + } + } + + /// + /// Convenience method to set this entry's group and user ids. + /// + /// + /// This entry's new user id. + /// + /// + /// This entry's new group id. + /// + public void SetIds(int userId, int groupId) + { + UserId = userId; + GroupId = groupId; + } + + /// + /// Convenience method to set this entry's group and user names. + /// + /// + /// This entry's new user name. + /// + /// + /// This entry's new group name. + /// + public void SetNames(string userName, string groupName) + { + UserName = userName; + GroupName = groupName; + } + + /// + /// Get/Set the modification time for this entry + /// + public DateTime ModTime + { + get + { + return header.ModTime; + } + set + { + header.ModTime = value; + } + } + + /// + /// Get this entry's file. + /// + /// + /// This entry's file. + /// + public string File + { + get + { + return file; + } + } + + /// + /// Get/set this entry's recorded file size. + /// + public long Size + { + get + { + return header.Size; + } + set + { + header.Size = value; + } + } + + /// + /// Return true if this entry represents a directory, false otherwise + /// + /// + /// True if this entry is a directory. + /// + public bool IsDirectory + { + get + { + if (file != null) + { + return Directory.Exists(file); + } + + if (header != null) + { + if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal)) + { + return true; + } + } + return false; + } + } + + /// + /// Fill in a TarHeader with information from a File. + /// + /// + /// The TarHeader to fill in. + /// + /// + /// The file from which to get the header information. + /// + public void GetFileTarHeader(TarHeader header, string file) + { + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } + + this.file = file ?? throw new ArgumentNullException(nameof(file)); + + // bugfix from torhovl from #D forum: + var name = file; + + // 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory + if (name.IndexOf(Directory.GetCurrentDirectory(), StringComparison.Ordinal) == 0) + { + name = name[Directory.GetCurrentDirectory().Length..]; + } + + /* + if (Path.DirectorySeparatorChar == '\\') + { + // check if the OS is Windows + // Strip off drive letters! + if (name.Length > 2) + { + char ch1 = name[0]; + char ch2 = name[1]; + + if (ch2 == ':' && Char.IsLetter(ch1)) + { + name = name.Substring(2); + } + } + } + */ + + name = name.Replace(Path.DirectorySeparatorChar, '/'); + + // No absolute pathnames + // Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\", + // so we loop on starting /'s. + while (name.StartsWith("/", StringComparison.Ordinal)) + { + name = name[1..]; + } + + header.LinkName = string.Empty; + header.Name = name; + + if (Directory.Exists(file)) + { + header.Mode = 1003; // Magic number for security access for a UNIX filesystem + header.TypeFlag = TarHeader.LF_DIR; + if ((header.Name.Length == 0) || header.Name[^1] != '/') + { + header.Name += "/"; + } + + header.Size = 0; + } + else + { + header.Mode = 33216; // Magic number for security access for a UNIX filesystem + header.TypeFlag = TarHeader.LF_NORMAL; + header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length; + } + + header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime(); + header.DevMajor = 0; + header.DevMinor = 0; + } + + /// + /// Get entries for all files present in this entries directory. + /// If this entry doesnt represent a directory zero entries are returned. + /// + /// + /// An array of TarEntry's for this entry's children. + /// + public TarEntry[] GetDirectoryEntries() + { + if ((file == null) || !Directory.Exists(file)) + { + return Empty.Array(); + } + + var list = Directory.GetFileSystemEntries(file); + var result = new TarEntry[list.Length]; + + for (var i = 0; i < list.Length; ++i) + { + result[i] = TarEntry.CreateEntryFromFile(list[i]); + } + + return result; + } + + /// + /// Write an entry's header information to a header buffer. + /// + /// + /// The tar entry header buffer to fill in. + /// + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public void WriteEntryHeader(byte[] outBuffer) + { + WriteEntryHeader(outBuffer, null); + } + + /// + /// Write an entry's header information to a header buffer. + /// + /// + /// The tar entry header buffer to fill in. + /// + /// + /// The used for the Name fields, or null for ASCII only + /// + public void WriteEntryHeader(byte[] outBuffer, Encoding nameEncoding) + { + header.WriteHeader(outBuffer, nameEncoding); + } + + /// + /// Convenience method that will modify an entry's name directly + /// in place in an entry header buffer byte array. + /// + /// + /// The buffer containing the entry header to modify. + /// + /// + /// The new name to place into the header buffer. + /// + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public static void AdjustEntryName(byte[] buffer, string newName) + { + AdjustEntryName(buffer, newName, null); + } + + /// + /// Convenience method that will modify an entry's name directly + /// in place in an entry header buffer byte array. + /// + /// + /// The buffer containing the entry header to modify. + /// + /// + /// The new name to place into the header buffer. + /// + /// + /// The used for the Name fields, or null for ASCII only + /// + public static void AdjustEntryName(byte[] buffer, string newName, Encoding nameEncoding) + { + TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN, nameEncoding); + } + + /// + /// Fill in a TarHeader given only the entry's name. + /// + /// + /// The TarHeader to fill in. + /// + /// + /// The tar entry name. + /// + public static void NameTarHeader(TarHeader header, string name) + { + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var isDir = name.EndsWith("/", StringComparison.Ordinal); + + header.Name = name; + header.Mode = isDir ? 1003 : 33216; + header.UserId = 0; + header.GroupId = 0; + header.Size = 0; + + header.ModTime = DateTime.UtcNow; + + header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL; + + header.LinkName = string.Empty; + header.UserName = string.Empty; + header.GroupName = string.Empty; + + header.DevMajor = 0; + header.DevMinor = 0; + } + + #region Instance Fields + + /// + /// The name of the file this entry represents or null if the entry is not based on a file. + /// + private string file; + + /// + /// The entry's header information. + /// + private TarHeader header; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs index 10f1deb63..cb6a9f57e 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs @@ -1,54 +1,53 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// TarException represents exceptions specific to Tar classes and code. +/// +[Serializable] +public class TarException : SharpZipBaseException { - /// - /// TarException represents exceptions specific to Tar classes and code. - /// - [Serializable] - public class TarException : SharpZipBaseException - { - /// - /// Initialise a new instance of . - /// - public TarException() - { - } + /// + /// Initialise a new instance of . + /// + public TarException() + { + } - /// - /// Initialise a new instance of with its message string. - /// - /// A that describes the error. - public TarException(string message) - : base(message) - { - } + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public TarException(string message) + : base(message) + { + } - /// - /// Initialise a new instance of . - /// - /// A that describes the error. - /// The that caused this exception. - public TarException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public TarException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the TarException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected TarException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the TarException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected TarException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs index d7fb74a48..23f7d2ab0 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs @@ -1,99 +1,98 @@ using System.Collections.Generic; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// Reads the extended header of a Tar stream +/// +public class TarExtendedHeaderReader { - /// - /// Reads the extended header of a Tar stream - /// - public class TarExtendedHeaderReader - { - private const byte LENGTH = 0; - private const byte KEY = 1; - private const byte VALUE = 2; - private const byte END = 3; - - private readonly Dictionary headers = new Dictionary(); - - private string[] headerParts = new string[3]; - - private int bbIndex; - private byte[] byteBuffer; - private char[] charBuffer; - - private readonly StringBuilder sb = new StringBuilder(); - private readonly Decoder decoder = Encoding.UTF8.GetDecoder(); - - private int state = LENGTH; - - private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' }; - - /// - /// Creates a new . - /// - public TarExtendedHeaderReader() - { - ResetBuffers(); - } - - /// - /// Read bytes from - /// - /// - /// - public void Read(byte[] buffer, int length) - { - for (int i = 0; i < length; i++) - { - byte next = buffer[i]; - - if (next == StateNext[state]) - { - Flush(); - headerParts[state] = sb.ToString(); - sb.Remove(0, sb.Length); - - if (++state == END) - { - headers.Add(headerParts[KEY], headerParts[VALUE]); - headerParts = new string[3]; - state = LENGTH; - } - } - else - { - byteBuffer[bbIndex++] = next; - if (bbIndex == 4) - Flush(); - } - } - } - - private void Flush() - { - decoder.Convert(byteBuffer, 0, bbIndex, charBuffer, 0, 4, false, out int bytesUsed, out int charsUsed, out bool completed); - - sb.Append(charBuffer, 0, charsUsed); - ResetBuffers(); - } - - private void ResetBuffers() - { - charBuffer = new char[4]; - byteBuffer = new byte[4]; - bbIndex = 0; - } - - /// - /// Returns the parsed headers as key-value strings - /// - public Dictionary Headers - { - get - { - // TODO: Check for invalid state? -NM 2018-07-01 - return headers; - } - } - } + private const byte LENGTH = 0; + private const byte KEY = 1; + private const byte VALUE = 2; + private const byte END = 3; + + private readonly Dictionary headers = []; + + private string[] headerParts = new string[3]; + + private int bbIndex; + private byte[] byteBuffer; + private char[] charBuffer; + + private readonly StringBuilder sb = new(); + private readonly Decoder decoder = Encoding.UTF8.GetDecoder(); + + private int state = LENGTH; + + private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' }; + + /// + /// Creates a new . + /// + public TarExtendedHeaderReader() + { + ResetBuffers(); + } + + /// + /// Read bytes from + /// + /// + /// + public void Read(byte[] buffer, int length) + { + for (var i = 0; i < length; i++) + { + var next = buffer[i]; + + if (next == StateNext[state]) + { + Flush(); + headerParts[state] = sb.ToString(); + sb.Remove(0, sb.Length); + + if (++state == END) + { + headers.Add(headerParts[KEY], headerParts[VALUE]); + headerParts = new string[3]; + state = LENGTH; + } + } + else + { + byteBuffer[bbIndex++] = next; + if (bbIndex == 4) + Flush(); + } + } + } + + private void Flush() + { + decoder.Convert(byteBuffer, 0, bbIndex, charBuffer, 0, 4, false, out _, out var charsUsed, out _); + + sb.Append(charBuffer, 0, charsUsed); + ResetBuffers(); + } + + private void ResetBuffers() + { + charBuffer = new char[4]; + byteBuffer = new byte[4]; + bbIndex = 0; + } + + /// + /// Returns the parsed headers as key-value strings + /// + public Dictionary Headers + { + get + { + // TODO: Check for invalid state? -NM 2018-07-01 + return headers; + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs index d49ce2304..eb2f7fa94 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs @@ -1,1310 +1,1270 @@ using System; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// This class encapsulates the Tar Entry Header used in Tar Archives. +/// The class also holds a number of tar constants, used mostly in headers. +/// +/// +/// The tar format and its POSIX successor PAX have a long history which makes for compatability +/// issues when creating and reading files. +/// +/// This is further complicated by a large number of programs with variations on formats +/// One common issue is the handling of names longer than 100 characters. +/// GNU style long names are currently supported. +/// +/// This is the ustar (Posix 1003.1) header. +/// +/// struct header +/// { +/// char t_name[100]; // 0 Filename +/// char t_mode[8]; // 100 Permissions +/// char t_uid[8]; // 108 Numerical User ID +/// char t_gid[8]; // 116 Numerical Group ID +/// char t_size[12]; // 124 Filesize +/// char t_mtime[12]; // 136 st_mtime +/// char t_chksum[8]; // 148 Checksum +/// char t_typeflag; // 156 Type of File +/// char t_linkname[100]; // 157 Target of Links +/// char t_magic[6]; // 257 "ustar" or other... +/// char t_version[2]; // 263 Version fixed to 00 +/// char t_uname[32]; // 265 User Name +/// char t_gname[32]; // 297 Group Name +/// char t_devmajor[8]; // 329 Major for devices +/// char t_devminor[8]; // 337 Minor for devices +/// char t_prefix[155]; // 345 Prefix for t_name +/// char t_mfill[12]; // 500 Filler up to 512 +/// }; +/// +public class TarHeader { - /// - /// This class encapsulates the Tar Entry Header used in Tar Archives. - /// The class also holds a number of tar constants, used mostly in headers. - /// - /// - /// The tar format and its POSIX successor PAX have a long history which makes for compatability - /// issues when creating and reading files. - /// - /// This is further complicated by a large number of programs with variations on formats - /// One common issue is the handling of names longer than 100 characters. - /// GNU style long names are currently supported. - /// - /// This is the ustar (Posix 1003.1) header. - /// - /// struct header - /// { - /// char t_name[100]; // 0 Filename - /// char t_mode[8]; // 100 Permissions - /// char t_uid[8]; // 108 Numerical User ID - /// char t_gid[8]; // 116 Numerical Group ID - /// char t_size[12]; // 124 Filesize - /// char t_mtime[12]; // 136 st_mtime - /// char t_chksum[8]; // 148 Checksum - /// char t_typeflag; // 156 Type of File - /// char t_linkname[100]; // 157 Target of Links - /// char t_magic[6]; // 257 "ustar" or other... - /// char t_version[2]; // 263 Version fixed to 00 - /// char t_uname[32]; // 265 User Name - /// char t_gname[32]; // 297 Group Name - /// char t_devmajor[8]; // 329 Major for devices - /// char t_devminor[8]; // 337 Minor for devices - /// char t_prefix[155]; // 345 Prefix for t_name - /// char t_mfill[12]; // 500 Filler up to 512 - /// }; - /// - public class TarHeader - { - #region Constants - - /// - /// The length of the name field in a header buffer. - /// - public const int NAMELEN = 100; - - /// - /// The length of the mode field in a header buffer. - /// - public const int MODELEN = 8; - - /// - /// The length of the user id field in a header buffer. - /// - public const int UIDLEN = 8; - - /// - /// The length of the group id field in a header buffer. - /// - public const int GIDLEN = 8; - - /// - /// The length of the checksum field in a header buffer. - /// - public const int CHKSUMLEN = 8; - - /// - /// Offset of checksum in a header buffer. - /// - public const int CHKSUMOFS = 148; - - /// - /// The length of the size field in a header buffer. - /// - public const int SIZELEN = 12; - - /// - /// The length of the magic field in a header buffer. - /// - public const int MAGICLEN = 6; - - /// - /// The length of the version field in a header buffer. - /// - public const int VERSIONLEN = 2; - - /// - /// The length of the modification time field in a header buffer. - /// - public const int MODTIMELEN = 12; - - /// - /// The length of the user name field in a header buffer. - /// - public const int UNAMELEN = 32; - - /// - /// The length of the group name field in a header buffer. - /// - public const int GNAMELEN = 32; - - /// - /// The length of the devices field in a header buffer. - /// - public const int DEVLEN = 8; - - /// - /// The length of the name prefix field in a header buffer. - /// - public const int PREFIXLEN = 155; - - // - // LF_ constants represent the "type" of an entry - // - - /// - /// The "old way" of indicating a normal file. - /// - public const byte LF_OLDNORM = 0; - - /// - /// Normal file type. - /// - public const byte LF_NORMAL = (byte)'0'; - - /// - /// Link file type. - /// - public const byte LF_LINK = (byte)'1'; - - /// - /// Symbolic link file type. - /// - public const byte LF_SYMLINK = (byte)'2'; - - /// - /// Character device file type. - /// - public const byte LF_CHR = (byte)'3'; - - /// - /// Block device file type. - /// - public const byte LF_BLK = (byte)'4'; - - /// - /// Directory file type. - /// - public const byte LF_DIR = (byte)'5'; - - /// - /// FIFO (pipe) file type. - /// - public const byte LF_FIFO = (byte)'6'; - - /// - /// Contiguous file type. - /// - public const byte LF_CONTIG = (byte)'7'; - - /// - /// Posix.1 2001 global extended header - /// - public const byte LF_GHDR = (byte)'g'; - - /// - /// Posix.1 2001 extended header - /// - public const byte LF_XHDR = (byte)'x'; - - // POSIX allows for upper case ascii type as extensions - - /// - /// Solaris access control list file type - /// - public const byte LF_ACL = (byte)'A'; - - /// - /// GNU dir dump file type - /// This is a dir entry that contains the names of files that were in the - /// dir at the time the dump was made - /// - public const byte LF_GNU_DUMPDIR = (byte)'D'; - - /// - /// Solaris Extended Attribute File - /// - public const byte LF_EXTATTR = (byte)'E'; - - /// - /// Inode (metadata only) no file content - /// - public const byte LF_META = (byte)'I'; - - /// - /// Identifies the next file on the tape as having a long link name - /// - public const byte LF_GNU_LONGLINK = (byte)'K'; - - /// - /// Identifies the next file on the tape as having a long name - /// - public const byte LF_GNU_LONGNAME = (byte)'L'; - - /// - /// Continuation of a file that began on another volume - /// - public const byte LF_GNU_MULTIVOL = (byte)'M'; - - /// - /// For storing filenames that dont fit in the main header (old GNU) - /// - public const byte LF_GNU_NAMES = (byte)'N'; - - /// - /// GNU Sparse file - /// - public const byte LF_GNU_SPARSE = (byte)'S'; - - /// - /// GNU Tape/volume header ignore on extraction - /// - public const byte LF_GNU_VOLHDR = (byte)'V'; - - /// - /// The magic tag representing a POSIX tar archive. (would be written with a trailing NULL) - /// - public const string TMAGIC = "ustar"; - - /// - /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it - /// - public const string GNU_TMAGIC = "ustar "; - - private const long timeConversionFactor = 10000000L; // 1 tick == 100 nanoseconds - private static readonly DateTime dateTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0); - - #endregion Constants - - #region Constructors - - /// - /// Initialise a default TarHeader instance - /// - public TarHeader() - { - Magic = TMAGIC; - Version = " "; - - Name = ""; - LinkName = ""; - - UserId = defaultUserId; - GroupId = defaultGroupId; - UserName = defaultUser; - GroupName = defaultGroupName; - Size = 0; - } - - #endregion Constructors - - #region Properties - - /// - /// Get/set the name for this tar entry. - /// - /// Thrown when attempting to set the property to null. - public string Name - { - get { return name; } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - name = value; - } - } - - /// - /// Get the name of this entry. - /// - /// The entry's name. - [Obsolete("Use the Name property instead", true)] - public string GetName() - { - return name; - } - - /// - /// Get/set the entry's Unix style permission mode. - /// - public int Mode - { - get { return mode; } - set { mode = value; } - } - - /// - /// The entry's user id. - /// - /// - /// This is only directly relevant to unix systems. - /// The default is zero. - /// - public int UserId - { - get { return userId; } - set { userId = value; } - } - - /// - /// Get/set the entry's group id. - /// - /// - /// This is only directly relevant to linux/unix systems. - /// The default value is zero. - /// - public int GroupId - { - get { return groupId; } - set { groupId = value; } - } - - /// - /// Get/set the entry's size. - /// - /// Thrown when setting the size to less than zero. - public long Size - { - get { return size; } - set - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "Cannot be less than zero"); - } - size = value; - } - } - - /// - /// Get/set the entry's modification time. - /// - /// - /// The modification time is only accurate to within a second. - /// - /// Thrown when setting the date time to less than 1/1/1970. - public DateTime ModTime - { - get { return modTime; } - set - { - if (value < dateTime1970) - { - throw new ArgumentOutOfRangeException(nameof(value), "ModTime cannot be before Jan 1st 1970"); - } - modTime = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second); - } - } - - /// - /// Get the entry's checksum. This is only valid/updated after writing or reading an entry. - /// - public int Checksum - { - get { return checksum; } - } - - /// - /// Get value of true if the header checksum is valid, false otherwise. - /// - public bool IsChecksumValid - { - get { return isChecksumValid; } - } - - /// - /// Get/set the entry's type flag. - /// - public byte TypeFlag - { - get { return typeFlag; } - set { typeFlag = value; } - } - - /// - /// The entry's link name. - /// - /// Thrown when attempting to set LinkName to null. - public string LinkName - { - get { return linkName; } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - linkName = value; - } - } - - /// - /// Get/set the entry's magic tag. - /// - /// Thrown when attempting to set Magic to null. - public string Magic - { - get { return magic; } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - magic = value; - } - } - - /// - /// The entry's version. - /// - /// Thrown when attempting to set Version to null. - public string Version - { - get - { - return version; - } - - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - version = value; - } - } - - /// - /// The entry's user name. - /// - public string UserName - { - get { return userName; } - set - { - if (value != null) - { - userName = value.Substring(0, Math.Min(UNAMELEN, value.Length)); - } - else - { - string currentUser = "user"; - if (currentUser.Length > UNAMELEN) - { - currentUser = currentUser.Substring(0, UNAMELEN); - } - userName = currentUser; - } - } - } - - /// - /// Get/set the entry's group name. - /// - /// - /// This is only directly relevant to unix systems. - /// - public string GroupName - { - get { return groupName; } - set - { - if (value == null) - { - groupName = "None"; - } - else - { - groupName = value; - } - } - } - - /// - /// Get/set the entry's major device number. - /// - public int DevMajor - { - get { return devMajor; } - set { devMajor = value; } - } - - /// - /// Get/set the entry's minor device number. - /// - public int DevMinor - { - get { return devMinor; } - set { devMinor = value; } - } - - #endregion Properties - - #region ICloneable Members - - /// - /// Create a new that is a copy of the current instance. - /// - /// A new that is a copy of the current instance. - public object Clone() - { - return this.MemberwiseClone(); - } - - #endregion ICloneable Members - - /// - /// Parse TarHeader information from a header buffer. - /// - /// - /// The tar entry header buffer to get information from. - /// - /// - /// The used for the Name field, or null for ASCII only - /// - public void ParseBuffer(byte[] header, Encoding nameEncoding) - { - if (header == null) - { - throw new ArgumentNullException(nameof(header)); - } - - int offset = 0; - - name = ParseName(header, offset, NAMELEN, nameEncoding).ToString(); - offset += NAMELEN; - - mode = (int)ParseOctal(header, offset, MODELEN); - offset += MODELEN; - - UserId = (int)ParseOctal(header, offset, UIDLEN); - offset += UIDLEN; - - GroupId = (int)ParseOctal(header, offset, GIDLEN); - offset += GIDLEN; - - Size = ParseBinaryOrOctal(header, offset, SIZELEN); - offset += SIZELEN; - - ModTime = GetDateTimeFromCTime(ParseOctal(header, offset, MODTIMELEN)); - offset += MODTIMELEN; - - checksum = (int)ParseOctal(header, offset, CHKSUMLEN); - offset += CHKSUMLEN; - - TypeFlag = header[offset++]; - - LinkName = ParseName(header, offset, NAMELEN, nameEncoding).ToString(); - offset += NAMELEN; - - Magic = ParseName(header, offset, MAGICLEN, nameEncoding).ToString(); - offset += MAGICLEN; - - if (Magic == "ustar") - { - Version = ParseName(header, offset, VERSIONLEN, nameEncoding).ToString(); - offset += VERSIONLEN; - - UserName = ParseName(header, offset, UNAMELEN, nameEncoding).ToString(); - offset += UNAMELEN; - - GroupName = ParseName(header, offset, GNAMELEN, nameEncoding).ToString(); - offset += GNAMELEN; - - DevMajor = (int)ParseOctal(header, offset, DEVLEN); - offset += DEVLEN; - - DevMinor = (int)ParseOctal(header, offset, DEVLEN); - offset += DEVLEN; - - string prefix = ParseName(header, offset, PREFIXLEN, nameEncoding).ToString(); - if (!string.IsNullOrEmpty(prefix)) Name = prefix + '/' + Name; - } - - isChecksumValid = Checksum == TarHeader.MakeCheckSum(header); - } - - /// - /// Parse TarHeader information from a header buffer. - /// - /// - /// The tar entry header buffer to get information from. - /// - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public void ParseBuffer(byte[] header) - { - ParseBuffer(header, null); - } - - /// - /// 'Write' header information to buffer provided, updating the check sum. - /// - /// output buffer for header information - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public void WriteHeader(byte[] outBuffer) - { - WriteHeader(outBuffer, null); - } - - /// - /// 'Write' header information to buffer provided, updating the check sum. - /// - /// output buffer for header information - /// The used for the Name field, or null for ASCII only - public void WriteHeader(byte[] outBuffer, Encoding nameEncoding) - { - if (outBuffer == null) - { - throw new ArgumentNullException(nameof(outBuffer)); - } - - int offset = 0; - - offset = GetNameBytes(Name, outBuffer, offset, NAMELEN, nameEncoding); - offset = GetOctalBytes(mode, outBuffer, offset, MODELEN); - offset = GetOctalBytes(UserId, outBuffer, offset, UIDLEN); - offset = GetOctalBytes(GroupId, outBuffer, offset, GIDLEN); - - offset = GetBinaryOrOctalBytes(Size, outBuffer, offset, SIZELEN); - offset = GetOctalBytes(GetCTime(ModTime), outBuffer, offset, MODTIMELEN); - - int csOffset = offset; - for (int c = 0; c < CHKSUMLEN; ++c) - { - outBuffer[offset++] = (byte)' '; - } - - outBuffer[offset++] = TypeFlag; - - offset = GetNameBytes(LinkName, outBuffer, offset, NAMELEN, nameEncoding); - offset = GetAsciiBytes(Magic, 0, outBuffer, offset, MAGICLEN, nameEncoding); - offset = GetNameBytes(Version, outBuffer, offset, VERSIONLEN, nameEncoding); - offset = GetNameBytes(UserName, outBuffer, offset, UNAMELEN, nameEncoding); - offset = GetNameBytes(GroupName, outBuffer, offset, GNAMELEN, nameEncoding); - - if ((TypeFlag == LF_CHR) || (TypeFlag == LF_BLK)) - { - offset = GetOctalBytes(DevMajor, outBuffer, offset, DEVLEN); - offset = GetOctalBytes(DevMinor, outBuffer, offset, DEVLEN); - } - - for (; offset < outBuffer.Length;) - { - outBuffer[offset++] = 0; - } - - checksum = ComputeCheckSum(outBuffer); - - GetCheckSumOctalBytes(checksum, outBuffer, csOffset, CHKSUMLEN); - isChecksumValid = true; - } - - /// - /// Get a hash code for the current object. - /// - /// A hash code for the current object. - public override int GetHashCode() - { - return Name.GetHashCode(); - } - - /// - /// Determines if this instance is equal to the specified object. - /// - /// The object to compare with. - /// true if the objects are equal, false otherwise. - public override bool Equals(object obj) - { - var localHeader = obj as TarHeader; - - bool result; - if (localHeader != null) - { - result = (name == localHeader.name) - && (mode == localHeader.mode) - && (UserId == localHeader.UserId) - && (GroupId == localHeader.GroupId) - && (Size == localHeader.Size) - && (ModTime == localHeader.ModTime) - && (Checksum == localHeader.Checksum) - && (TypeFlag == localHeader.TypeFlag) - && (LinkName == localHeader.LinkName) - && (Magic == localHeader.Magic) - && (Version == localHeader.Version) - && (UserName == localHeader.UserName) - && (GroupName == localHeader.GroupName) - && (DevMajor == localHeader.DevMajor) - && (DevMinor == localHeader.DevMinor); - } - else - { - result = false; - } - return result; - } - - /// - /// Set defaults for values used when constructing a TarHeader instance. - /// - /// Value to apply as a default for userId. - /// Value to apply as a default for userName. - /// Value to apply as a default for groupId. - /// Value to apply as a default for groupName. - static internal void SetValueDefaults(int userId, string userName, int groupId, string groupName) - { - defaultUserId = userIdAsSet = userId; - defaultUser = userNameAsSet = userName; - defaultGroupId = groupIdAsSet = groupId; - defaultGroupName = groupNameAsSet = groupName; - } - - static internal void RestoreSetValues() - { - defaultUserId = userIdAsSet; - defaultUser = userNameAsSet; - defaultGroupId = groupIdAsSet; - defaultGroupName = groupNameAsSet; - } - - // Return value that may be stored in octal or binary. Length must exceed 8. - // - static private long ParseBinaryOrOctal(byte[] header, int offset, int length) - { - if (header[offset] >= 0x80) - { - // File sizes over 8GB are stored in 8 right-justified bytes of binary indicated by setting the high-order bit of the leftmost byte of a numeric field. - long result = 0; - for (int pos = length - 8; pos < length; pos++) - { - result = result << 8 | header[offset + pos]; - } - return result; - } - return ParseOctal(header, offset, length); - } - - /// - /// Parse an octal string from a header buffer. - /// - /// The header buffer from which to parse. - /// The offset into the buffer from which to parse. - /// The number of header bytes to parse. - /// The long equivalent of the octal string. - static public long ParseOctal(byte[] header, int offset, int length) - { - if (header == null) - { - throw new ArgumentNullException(nameof(header)); - } - - long result = 0; - bool stillPadding = true; - - int end = offset + length; - for (int i = offset; i < end; ++i) - { - if (header[i] == 0) - { - break; - } - - if (header[i] == (byte)' ' || header[i] == '0') - { - if (stillPadding) - { - continue; - } - - if (header[i] == (byte)' ') - { - break; - } - } - - stillPadding = false; - - result = (result << 3) + (header[i] - '0'); - } - - return result; - } - - /// - /// Parse a name from a header buffer. - /// - /// - /// The header buffer from which to parse. - /// - /// - /// The offset into the buffer from which to parse. - /// - /// - /// The number of header bytes to parse. - /// - /// - /// The name parsed. - /// - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - static public StringBuilder ParseName(byte[] header, int offset, int length) - { - return ParseName(header, offset, length, null); - } - - /// - /// Parse a name from a header buffer. - /// - /// - /// The header buffer from which to parse. - /// - /// - /// The offset into the buffer from which to parse. - /// - /// - /// The number of header bytes to parse. - /// - /// - /// name encoding, or null for ASCII only - /// - /// - /// The name parsed. - /// - static public StringBuilder ParseName(byte[] header, int offset, int length, Encoding encoding) - { - if (header == null) - { - throw new ArgumentNullException(nameof(header)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be less than zero"); - } - - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length), "Cannot be less than zero"); - } - - if (offset + length > header.Length) - { - throw new ArgumentException("Exceeds header size", nameof(length)); - } - - var result = new StringBuilder(length); - - int count = 0; - if(encoding == null) - { - for (int i = offset; i < offset + length; ++i) - { - if (header[i] == 0) - { - break; - } - result.Append((char)header[i]); - } - } - else - { - for(int i = offset; i < offset + length; ++i, ++count) - { - if(header[i] == 0) - { - break; - } - } - result.Append(encoding.GetString(header, offset, count)); - } - - return result; - } - - /// - /// Add name to the buffer as a collection of bytes - /// - /// The name to add - /// The offset of the first character - /// The buffer to add to - /// The index of the first byte to add - /// The number of characters/bytes to add - /// The next free index in the - public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buffer, int bufferOffset, int length) - { - return GetNameBytes(name.ToString(), nameOffset, buffer, bufferOffset, length, null); - } - - /// - /// Add name to the buffer as a collection of bytes - /// - /// The name to add - /// The offset of the first character - /// The buffer to add to - /// The index of the first byte to add - /// The number of characters/bytes to add - /// The next free index in the - public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length) - { - return GetNameBytes(name, nameOffset, buffer, bufferOffset, length, null); - } - - /// - /// Add name to the buffer as a collection of bytes - /// - /// The name to add - /// The offset of the first character - /// The buffer to add to - /// The index of the first byte to add - /// The number of characters/bytes to add - /// name encoding, or null for ASCII only - /// The next free index in the - public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length, Encoding encoding) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - int i; - if(encoding != null) - { - // it can be more sufficient if using Span or unsafe - var nameArray = name.ToCharArray(nameOffset, Math.Min(name.Length - nameOffset, length)); - // it can be more sufficient if using Span(or unsafe?) and ArrayPool for temporary buffer - var bytes = encoding.GetBytes(nameArray, 0, nameArray.Length); - i = Math.Min(bytes.Length, length); - Array.Copy(bytes, 0, buffer, bufferOffset, i); - } - else - { - for (i = 0; i < length && nameOffset + i < name.Length; ++i) - { - buffer[bufferOffset + i] = (byte)name[nameOffset + i]; - } - } - - for (; i < length; ++i) - { - buffer[bufferOffset + i] = 0; - } - return bufferOffset + length; - } - /// - /// Add an entry name to the buffer - /// - /// - /// The name to add - /// - /// - /// The buffer to add to - /// - /// - /// The offset into the buffer from which to start adding - /// - /// - /// The number of header bytes to add - /// - /// - /// The index of the next free byte in the buffer - /// - /// TODO: what should be default behavior?(omit upper byte or UTF8?) - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length) - { - return GetNameBytes(name, buffer, offset, length, null); - } - - /// - /// Add an entry name to the buffer - /// - /// - /// The name to add - /// - /// - /// The buffer to add to - /// - /// - /// The offset into the buffer from which to start adding - /// - /// - /// The number of header bytes to add - /// - /// - /// - /// - /// The index of the next free byte in the buffer - /// - public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length, Encoding encoding) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - return GetNameBytes(name.ToString(), 0, buffer, offset, length, encoding); - } - - /// - /// Add an entry name to the buffer - /// - /// The name to add - /// The buffer to add to - /// The offset into the buffer from which to start adding - /// The number of header bytes to add - /// The index of the next free byte in the buffer - /// TODO: what should be default behavior?(omit upper byte or UTF8?) - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public static int GetNameBytes(string name, byte[] buffer, int offset, int length) - { - return GetNameBytes(name, buffer, offset, length, null); - } - - /// - /// Add an entry name to the buffer - /// - /// The name to add - /// The buffer to add to - /// The offset into the buffer from which to start adding - /// The number of header bytes to add - /// - /// The index of the next free byte in the buffer - public static int GetNameBytes(string name, byte[] buffer, int offset, int length, Encoding encoding) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - return GetNameBytes(name, 0, buffer, offset, length, encoding); - } - /// - /// Add a string to a buffer as a collection of ascii bytes. - /// - /// The string to add - /// The offset of the first character to add. - /// The buffer to add to. - /// The offset to start adding at. - /// The number of ascii characters to add. - /// The next free index in the buffer. - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length) - { - return GetAsciiBytes(toAdd, nameOffset, buffer, bufferOffset, length, null); - } - - /// - /// Add a string to a buffer as a collection of ascii bytes. - /// - /// The string to add - /// The offset of the first character to add. - /// The buffer to add to. - /// The offset to start adding at. - /// The number of ascii characters to add. - /// String encoding, or null for ASCII only - /// The next free index in the buffer. - public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length, Encoding encoding) - { - if (toAdd == null) - { - throw new ArgumentNullException(nameof(toAdd)); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - int i; - if(encoding == null) - { - for (i = 0; i < length && nameOffset + i < toAdd.Length; ++i) - { - buffer[bufferOffset + i] = (byte)toAdd[nameOffset + i]; - } - } - else - { - // It can be more sufficient if using unsafe code or Span(ToCharArray can be omitted) - var chars = toAdd.ToCharArray(); - // It can be more sufficient if using Span(or unsafe?) and ArrayPool for temporary buffer - var bytes = encoding.GetBytes(chars, nameOffset, Math.Min(toAdd.Length - nameOffset, length)); - i = Math.Min(bytes.Length, length); - Array.Copy(bytes, 0, buffer, bufferOffset, i); - } - // If length is beyond the toAdd string length (which is OK by the prev loop condition), eg if a field has fixed length and the string is shorter, make sure all of the extra chars are written as NULLs, so that the reader func would ignore them and get back the original string - for (; i < length; ++i) - buffer[bufferOffset + i] = 0; - return bufferOffset + length; - } - - /// - /// Put an octal representation of a value into a buffer - /// - /// - /// the value to be converted to octal - /// - /// - /// buffer to store the octal string - /// - /// - /// The offset into the buffer where the value starts - /// - /// - /// The length of the octal string to create - /// - /// - /// The offset of the character next byte after the octal string - /// - public static int GetOctalBytes(long value, byte[] buffer, int offset, int length) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - int localIndex = length - 1; - - // Either a space or null is valid here. We use NULL as per GNUTar - buffer[offset + localIndex] = 0; - --localIndex; - - if (value > 0) - { - for (long v = value; (localIndex >= 0) && (v > 0); --localIndex) - { - buffer[offset + localIndex] = (byte)((byte)'0' + (byte)(v & 7)); - v >>= 3; - } - } - - for (; localIndex >= 0; --localIndex) - { - buffer[offset + localIndex] = (byte)'0'; - } - - return offset + length; - } - - /// - /// Put an octal or binary representation of a value into a buffer - /// - /// Value to be convert to octal - /// The buffer to update - /// The offset into the buffer to store the value - /// The length of the octal string. Must be 12. - /// Index of next byte - private static int GetBinaryOrOctalBytes(long value, byte[] buffer, int offset, int length) - { - if (value > 0x1FFFFFFFF) - { // Octal 77777777777 (11 digits) - // Put value as binary, right-justified into the buffer. Set high order bit of left-most byte. - for (int pos = length - 1; pos > 0; pos--) - { - buffer[offset + pos] = (byte)value; - value = value >> 8; - } - buffer[offset] = 0x80; - return offset + length; - } - return GetOctalBytes(value, buffer, offset, length); - } - - /// - /// Add the checksum integer to header buffer. - /// - /// - /// The header buffer to set the checksum for - /// The offset into the buffer for the checksum - /// The number of header bytes to update. - /// It's formatted differently from the other fields: it has 6 digits, a - /// null, then a space -- rather than digits, a space, then a null. - /// The final space is already there, from checksumming - /// - /// The modified buffer offset - private static void GetCheckSumOctalBytes(long value, byte[] buffer, int offset, int length) - { - GetOctalBytes(value, buffer, offset, length - 1); - } - - /// - /// Compute the checksum for a tar entry header. - /// The checksum field must be all spaces prior to this happening - /// - /// The tar entry's header buffer. - /// The computed checksum. - private static int ComputeCheckSum(byte[] buffer) - { - int sum = 0; - for (int i = 0; i < buffer.Length; ++i) - { - sum += buffer[i]; - } - return sum; - } - - /// - /// Make a checksum for a tar entry ignoring the checksum contents. - /// - /// The tar entry's header buffer. - /// The checksum for the buffer - private static int MakeCheckSum(byte[] buffer) - { - int sum = 0; - for (int i = 0; i < CHKSUMOFS; ++i) - { - sum += buffer[i]; - } - - for (int i = 0; i < CHKSUMLEN; ++i) - { - sum += (byte)' '; - } - - for (int i = CHKSUMOFS + CHKSUMLEN; i < buffer.Length; ++i) - { - sum += buffer[i]; - } - return sum; - } - - private static int GetCTime(DateTime dateTime) - { - return unchecked((int)((dateTime.Ticks - dateTime1970.Ticks) / timeConversionFactor)); - } - - private static DateTime GetDateTimeFromCTime(long ticks) - { - DateTime result; - - try - { - result = new DateTime(dateTime1970.Ticks + ticks * timeConversionFactor); - } - catch (ArgumentOutOfRangeException) - { - result = dateTime1970; - } - return result; - } - - #region Instance Fields - - private string name; - private int mode; - private int userId; - private int groupId; - private long size; - private DateTime modTime; - private int checksum; - private bool isChecksumValid; - private byte typeFlag; - private string linkName; - private string magic; - private string version; - private string userName; - private string groupName; - private int devMajor; - private int devMinor; - - #endregion Instance Fields - - #region Class Fields - - // Values used during recursive operations. - static internal int userIdAsSet; - - static internal int groupIdAsSet; - static internal string userNameAsSet; - static internal string groupNameAsSet = "None"; - - static internal int defaultUserId; - static internal int defaultGroupId; - static internal string defaultGroupName = "None"; - static internal string defaultUser; - - #endregion Class Fields - } + #region Constants + + /// + /// The length of the name field in a header buffer. + /// + public const int NAMELEN = 100; + + /// + /// The length of the mode field in a header buffer. + /// + public const int MODELEN = 8; + + /// + /// The length of the user id field in a header buffer. + /// + public const int UIDLEN = 8; + + /// + /// The length of the group id field in a header buffer. + /// + public const int GIDLEN = 8; + + /// + /// The length of the checksum field in a header buffer. + /// + public const int CHKSUMLEN = 8; + + /// + /// Offset of checksum in a header buffer. + /// + public const int CHKSUMOFS = 148; + + /// + /// The length of the size field in a header buffer. + /// + public const int SIZELEN = 12; + + /// + /// The length of the magic field in a header buffer. + /// + public const int MAGICLEN = 6; + + /// + /// The length of the version field in a header buffer. + /// + public const int VERSIONLEN = 2; + + /// + /// The length of the modification time field in a header buffer. + /// + public const int MODTIMELEN = 12; + + /// + /// The length of the user name field in a header buffer. + /// + public const int UNAMELEN = 32; + + /// + /// The length of the group name field in a header buffer. + /// + public const int GNAMELEN = 32; + + /// + /// The length of the devices field in a header buffer. + /// + public const int DEVLEN = 8; + + /// + /// The length of the name prefix field in a header buffer. + /// + public const int PREFIXLEN = 155; + + // + // LF_ constants represent the "type" of an entry + // + + /// + /// The "old way" of indicating a normal file. + /// + public const byte LF_OLDNORM = 0; + + /// + /// Normal file type. + /// + public const byte LF_NORMAL = (byte)'0'; + + /// + /// Link file type. + /// + public const byte LF_LINK = (byte)'1'; + + /// + /// Symbolic link file type. + /// + public const byte LF_SYMLINK = (byte)'2'; + + /// + /// Character device file type. + /// + public const byte LF_CHR = (byte)'3'; + + /// + /// Block device file type. + /// + public const byte LF_BLK = (byte)'4'; + + /// + /// Directory file type. + /// + public const byte LF_DIR = (byte)'5'; + + /// + /// FIFO (pipe) file type. + /// + public const byte LF_FIFO = (byte)'6'; + + /// + /// Contiguous file type. + /// + public const byte LF_CONTIG = (byte)'7'; + + /// + /// Posix.1 2001 global extended header + /// + public const byte LF_GHDR = (byte)'g'; + + /// + /// Posix.1 2001 extended header + /// + public const byte LF_XHDR = (byte)'x'; + + // POSIX allows for upper case ascii type as extensions + + /// + /// Solaris access control list file type + /// + public const byte LF_ACL = (byte)'A'; + + /// + /// GNU dir dump file type + /// This is a dir entry that contains the names of files that were in the + /// dir at the time the dump was made + /// + public const byte LF_GNU_DUMPDIR = (byte)'D'; + + /// + /// Solaris Extended Attribute File + /// + public const byte LF_EXTATTR = (byte)'E'; + + /// + /// Inode (metadata only) no file content + /// + public const byte LF_META = (byte)'I'; + + /// + /// Identifies the next file on the tape as having a long link name + /// + public const byte LF_GNU_LONGLINK = (byte)'K'; + + /// + /// Identifies the next file on the tape as having a long name + /// + public const byte LF_GNU_LONGNAME = (byte)'L'; + + /// + /// Continuation of a file that began on another volume + /// + public const byte LF_GNU_MULTIVOL = (byte)'M'; + + /// + /// For storing filenames that dont fit in the main header (old GNU) + /// + public const byte LF_GNU_NAMES = (byte)'N'; + + /// + /// GNU Sparse file + /// + public const byte LF_GNU_SPARSE = (byte)'S'; + + /// + /// GNU Tape/volume header ignore on extraction + /// + public const byte LF_GNU_VOLHDR = (byte)'V'; + + /// + /// The magic tag representing a POSIX tar archive. (would be written with a trailing NULL) + /// + public const string TMAGIC = "ustar"; + + /// + /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it + /// + public const string GNU_TMAGIC = "ustar "; + + private const long timeConversionFactor = 10000000L; // 1 tick == 100 nanoseconds + private static readonly DateTime dateTime1970 = new(1970, 1, 1, 0, 0, 0, 0); + + #endregion Constants + + #region Constructors + + /// + /// Initialise a default TarHeader instance + /// + public TarHeader() + { + Magic = TMAGIC; + Version = " "; + + Name = ""; + LinkName = ""; + + UserId = defaultUserId; + GroupId = defaultGroupId; + UserName = defaultUser; + GroupName = defaultGroupName; + Size = 0; + } + + #endregion Constructors + + #region Properties + + /// + /// Get/set the name for this tar entry. + /// + /// Thrown when attempting to set the property to null. + public string Name + { + get { return name; } + set + { + name = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + /// + /// Get the name of this entry. + /// + /// The entry's name. + [Obsolete("Use the Name property instead", true)] + public string GetName() + { + return name; + } + + /// + /// Get/set the entry's Unix style permission mode. + /// + public int Mode + { + get { return mode; } + set { mode = value; } + } + + /// + /// The entry's user id. + /// + /// + /// This is only directly relevant to unix systems. + /// The default is zero. + /// + public int UserId + { + get { return userId; } + set { userId = value; } + } + + /// + /// Get/set the entry's group id. + /// + /// + /// This is only directly relevant to linux/unix systems. + /// The default value is zero. + /// + public int GroupId + { + get { return groupId; } + set { groupId = value; } + } + + /// + /// Get/set the entry's size. + /// + /// Thrown when setting the size to less than zero. + public long Size + { + get { return size; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "Cannot be less than zero"); + } + size = value; + } + } + + /// + /// Get/set the entry's modification time. + /// + /// + /// The modification time is only accurate to within a second. + /// + /// Thrown when setting the date time to less than 1/1/1970. + public DateTime ModTime + { + get { return modTime; } + set + { + if (value < dateTime1970) + { + throw new ArgumentOutOfRangeException(nameof(value), "ModTime cannot be before Jan 1st 1970"); + } + modTime = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second); + } + } + + /// + /// Get the entry's checksum. This is only valid/updated after writing or reading an entry. + /// + public int Checksum + { + get { return checksum; } + } + + /// + /// Get value of true if the header checksum is valid, false otherwise. + /// + public bool IsChecksumValid + { + get { return isChecksumValid; } + } + + /// + /// Get/set the entry's type flag. + /// + public byte TypeFlag + { + get { return typeFlag; } + set { typeFlag = value; } + } + + /// + /// The entry's link name. + /// + /// Thrown when attempting to set LinkName to null. + public string LinkName + { + get { return linkName; } + set + { + linkName = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + /// + /// Get/set the entry's magic tag. + /// + /// Thrown when attempting to set Magic to null. + public string Magic + { + get { return magic; } + set + { + magic = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + /// + /// The entry's version. + /// + /// Thrown when attempting to set Version to null. + public string Version + { + get + { + return version; + } + + set + { + version = value ?? throw new ArgumentNullException(nameof(value)); + } + } + + /// + /// The entry's user name. + /// + public string UserName + { + get { return userName; } + set + { + if (value != null) + { + userName = value[..Math.Min(UNAMELEN, value.Length)]; + } + else + { + var currentUser = "user"; + if (currentUser.Length > UNAMELEN) + { + currentUser = currentUser[..UNAMELEN]; + } + userName = currentUser; + } + } + } + + /// + /// Get/set the entry's group name. + /// + /// + /// This is only directly relevant to unix systems. + /// + public string GroupName + { + get { return groupName; } + set + { + groupName = value == null ? "None" : value; + } + } + + /// + /// Get/set the entry's major device number. + /// + public int DevMajor + { + get { return devMajor; } + set { devMajor = value; } + } + + /// + /// Get/set the entry's minor device number. + /// + public int DevMinor + { + get { return devMinor; } + set { devMinor = value; } + } + + #endregion Properties + + #region ICloneable Members + + /// + /// Create a new that is a copy of the current instance. + /// + /// A new that is a copy of the current instance. + public object Clone() + { + return this.MemberwiseClone(); + } + + #endregion ICloneable Members + + /// + /// Parse TarHeader information from a header buffer. + /// + /// + /// The tar entry header buffer to get information from. + /// + /// + /// The used for the Name field, or null for ASCII only + /// + public void ParseBuffer(byte[] header, Encoding nameEncoding) + { + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } + + var offset = 0; + + name = ParseName(header, offset, NAMELEN, nameEncoding).ToString(); + offset += NAMELEN; + + mode = (int)ParseOctal(header, offset, MODELEN); + offset += MODELEN; + + UserId = (int)ParseOctal(header, offset, UIDLEN); + offset += UIDLEN; + + GroupId = (int)ParseOctal(header, offset, GIDLEN); + offset += GIDLEN; + + Size = ParseBinaryOrOctal(header, offset, SIZELEN); + offset += SIZELEN; + + ModTime = GetDateTimeFromCTime(ParseOctal(header, offset, MODTIMELEN)); + offset += MODTIMELEN; + + checksum = (int)ParseOctal(header, offset, CHKSUMLEN); + offset += CHKSUMLEN; + + TypeFlag = header[offset++]; + + LinkName = ParseName(header, offset, NAMELEN, nameEncoding).ToString(); + offset += NAMELEN; + + Magic = ParseName(header, offset, MAGICLEN, nameEncoding).ToString(); + offset += MAGICLEN; + + if (Magic == "ustar") + { + Version = ParseName(header, offset, VERSIONLEN, nameEncoding).ToString(); + offset += VERSIONLEN; + + UserName = ParseName(header, offset, UNAMELEN, nameEncoding).ToString(); + offset += UNAMELEN; + + GroupName = ParseName(header, offset, GNAMELEN, nameEncoding).ToString(); + offset += GNAMELEN; + + DevMajor = (int)ParseOctal(header, offset, DEVLEN); + offset += DEVLEN; + + DevMinor = (int)ParseOctal(header, offset, DEVLEN); + offset += DEVLEN; + + var prefix = ParseName(header, offset, PREFIXLEN, nameEncoding).ToString(); + if (!string.IsNullOrEmpty(prefix)) + Name = prefix + '/' + Name; + } + + isChecksumValid = Checksum == TarHeader.MakeCheckSum(header); + } + + /// + /// Parse TarHeader information from a header buffer. + /// + /// + /// The tar entry header buffer to get information from. + /// + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public void ParseBuffer(byte[] header) + { + ParseBuffer(header, null); + } + + /// + /// 'Write' header information to buffer provided, updating the check sum. + /// + /// output buffer for header information + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public void WriteHeader(byte[] outBuffer) + { + WriteHeader(outBuffer, null); + } + + /// + /// 'Write' header information to buffer provided, updating the check sum. + /// + /// output buffer for header information + /// The used for the Name field, or null for ASCII only + public void WriteHeader(byte[] outBuffer, Encoding nameEncoding) + { + if (outBuffer == null) + { + throw new ArgumentNullException(nameof(outBuffer)); + } + + var offset = 0; + + offset = GetNameBytes(Name, outBuffer, offset, NAMELEN, nameEncoding); + offset = GetOctalBytes(mode, outBuffer, offset, MODELEN); + offset = GetOctalBytes(UserId, outBuffer, offset, UIDLEN); + offset = GetOctalBytes(GroupId, outBuffer, offset, GIDLEN); + + offset = GetBinaryOrOctalBytes(Size, outBuffer, offset, SIZELEN); + offset = GetOctalBytes(GetCTime(ModTime), outBuffer, offset, MODTIMELEN); + + var csOffset = offset; + for (var c = 0; c < CHKSUMLEN; ++c) + { + outBuffer[offset++] = (byte)' '; + } + + outBuffer[offset++] = TypeFlag; + + offset = GetNameBytes(LinkName, outBuffer, offset, NAMELEN, nameEncoding); + offset = GetAsciiBytes(Magic, 0, outBuffer, offset, MAGICLEN, nameEncoding); + offset = GetNameBytes(Version, outBuffer, offset, VERSIONLEN, nameEncoding); + offset = GetNameBytes(UserName, outBuffer, offset, UNAMELEN, nameEncoding); + offset = GetNameBytes(GroupName, outBuffer, offset, GNAMELEN, nameEncoding); + + if (TypeFlag is LF_CHR or LF_BLK) + { + offset = GetOctalBytes(DevMajor, outBuffer, offset, DEVLEN); + offset = GetOctalBytes(DevMinor, outBuffer, offset, DEVLEN); + } + + for (; offset < outBuffer.Length;) + { + outBuffer[offset++] = 0; + } + + checksum = ComputeCheckSum(outBuffer); + + GetCheckSumOctalBytes(checksum, outBuffer, csOffset, CHKSUMLEN); + isChecksumValid = true; + } + + /// + /// Get a hash code for the current object. + /// + /// A hash code for the current object. + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + /// + /// Determines if this instance is equal to the specified object. + /// + /// The object to compare with. + /// true if the objects are equal, false otherwise. + public override bool Equals(object obj) + { + var result = obj is TarHeader localHeader +&& (name == localHeader.name) + && (mode == localHeader.mode) + && (UserId == localHeader.UserId) + && (GroupId == localHeader.GroupId) + && (Size == localHeader.Size) + && (ModTime == localHeader.ModTime) + && (Checksum == localHeader.Checksum) + && (TypeFlag == localHeader.TypeFlag) + && (LinkName == localHeader.LinkName) + && (Magic == localHeader.Magic) + && (Version == localHeader.Version) + && (UserName == localHeader.UserName) + && (GroupName == localHeader.GroupName) + && (DevMajor == localHeader.DevMajor) + && (DevMinor == localHeader.DevMinor); + return result; + } + + /// + /// Set defaults for values used when constructing a TarHeader instance. + /// + /// Value to apply as a default for userId. + /// Value to apply as a default for userName. + /// Value to apply as a default for groupId. + /// Value to apply as a default for groupName. + internal static void SetValueDefaults(int userId, string userName, int groupId, string groupName) + { + defaultUserId = userIdAsSet = userId; + defaultUser = userNameAsSet = userName; + defaultGroupId = groupIdAsSet = groupId; + defaultGroupName = groupNameAsSet = groupName; + } + + internal static void RestoreSetValues() + { + defaultUserId = userIdAsSet; + defaultUser = userNameAsSet; + defaultGroupId = groupIdAsSet; + defaultGroupName = groupNameAsSet; + } + + // Return value that may be stored in octal or binary. Length must exceed 8. + // + private static long ParseBinaryOrOctal(byte[] header, int offset, int length) + { + if (header[offset] >= 0x80) + { + // File sizes over 8GB are stored in 8 right-justified bytes of binary indicated by setting the high-order bit of the leftmost byte of a numeric field. + long result = 0; + for (var pos = length - 8; pos < length; pos++) + { + result = (result << 8) | header[offset + pos]; + } + return result; + } + return ParseOctal(header, offset, length); + } + + /// + /// Parse an octal string from a header buffer. + /// + /// The header buffer from which to parse. + /// The offset into the buffer from which to parse. + /// The number of header bytes to parse. + /// The long equivalent of the octal string. + public static long ParseOctal(byte[] header, int offset, int length) + { + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } + + long result = 0; + var stillPadding = true; + + var end = offset + length; + for (var i = offset; i < end; ++i) + { + if (header[i] == 0) + { + break; + } + + if (header[i] is ((byte)' ') or (byte)'0') + { + if (stillPadding) + { + continue; + } + + if (header[i] == (byte)' ') + { + break; + } + } + + stillPadding = false; + + result = (result << 3) + (header[i] - '0'); + } + + return result; + } + + /// + /// Parse a name from a header buffer. + /// + /// + /// The header buffer from which to parse. + /// + /// + /// The offset into the buffer from which to parse. + /// + /// + /// The number of header bytes to parse. + /// + /// + /// The name parsed. + /// + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public static StringBuilder ParseName(byte[] header, int offset, int length) + { + return ParseName(header, offset, length, null); + } + + /// + /// Parse a name from a header buffer. + /// + /// + /// The header buffer from which to parse. + /// + /// + /// The offset into the buffer from which to parse. + /// + /// + /// The number of header bytes to parse. + /// + /// + /// name encoding, or null for ASCII only + /// + /// + /// The name parsed. + /// + public static StringBuilder ParseName(byte[] header, int offset, int length, Encoding encoding) + { + if (header == null) + { + throw new ArgumentNullException(nameof(header)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be less than zero"); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), "Cannot be less than zero"); + } + + if (offset + length > header.Length) + { + throw new ArgumentException("Exceeds header size", nameof(length)); + } + + var result = new StringBuilder(length); + + var count = 0; + if (encoding == null) + { + for (var i = offset; i < offset + length; ++i) + { + if (header[i] == 0) + { + break; + } + result.Append((char)header[i]); + } + } + else + { + for (var i = offset; i < offset + length; ++i, ++count) + { + if (header[i] == 0) + { + break; + } + } + result.Append(encoding.GetString(header, offset, count)); + } + + return result; + } + + /// + /// Add name to the buffer as a collection of bytes + /// + /// The name to add + /// The offset of the first character + /// The buffer to add to + /// The index of the first byte to add + /// The number of characters/bytes to add + /// The next free index in the + public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buffer, int bufferOffset, int length) + { + return GetNameBytes(name.ToString(), nameOffset, buffer, bufferOffset, length, null); + } + + /// + /// Add name to the buffer as a collection of bytes + /// + /// The name to add + /// The offset of the first character + /// The buffer to add to + /// The index of the first byte to add + /// The number of characters/bytes to add + /// The next free index in the + public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length) + { + return GetNameBytes(name, nameOffset, buffer, bufferOffset, length, null); + } + + /// + /// Add name to the buffer as a collection of bytes + /// + /// The name to add + /// The offset of the first character + /// The buffer to add to + /// The index of the first byte to add + /// The number of characters/bytes to add + /// name encoding, or null for ASCII only + /// The next free index in the + public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length, Encoding encoding) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + int i; + if (encoding != null) + { + // it can be more sufficient if using Span or unsafe + var nameArray = name.ToCharArray(nameOffset, Math.Min(name.Length - nameOffset, length)); + // it can be more sufficient if using Span(or unsafe?) and ArrayPool for temporary buffer + var bytes = encoding.GetBytes(nameArray, 0, nameArray.Length); + i = Math.Min(bytes.Length, length); + Array.Copy(bytes, 0, buffer, bufferOffset, i); + } + else + { + for (i = 0; i < length && nameOffset + i < name.Length; ++i) + { + buffer[bufferOffset + i] = (byte)name[nameOffset + i]; + } + } + + for (; i < length; ++i) + { + buffer[bufferOffset + i] = 0; + } + return bufferOffset + length; + } + /// + /// Add an entry name to the buffer + /// + /// + /// The name to add + /// + /// + /// The buffer to add to + /// + /// + /// The offset into the buffer from which to start adding + /// + /// + /// The number of header bytes to add + /// + /// + /// The index of the next free byte in the buffer + /// + /// TODO: what should be default behavior?(omit upper byte or UTF8?) + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length) + { + return GetNameBytes(name, buffer, offset, length, null); + } + + /// + /// Add an entry name to the buffer + /// + /// + /// The name to add + /// + /// + /// The buffer to add to + /// + /// + /// The offset into the buffer from which to start adding + /// + /// + /// The number of header bytes to add + /// + /// + /// + /// + /// The index of the next free byte in the buffer + /// + public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length, Encoding encoding) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return buffer == null + ? throw new ArgumentNullException(nameof(buffer)) + : GetNameBytes(name.ToString(), 0, buffer, offset, length, encoding); + } + + /// + /// Add an entry name to the buffer + /// + /// The name to add + /// The buffer to add to + /// The offset into the buffer from which to start adding + /// The number of header bytes to add + /// The index of the next free byte in the buffer + /// TODO: what should be default behavior?(omit upper byte or UTF8?) + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public static int GetNameBytes(string name, byte[] buffer, int offset, int length) + { + return GetNameBytes(name, buffer, offset, length, null); + } + + /// + /// Add an entry name to the buffer + /// + /// The name to add + /// The buffer to add to + /// The offset into the buffer from which to start adding + /// The number of header bytes to add + /// + /// The index of the next free byte in the buffer + public static int GetNameBytes(string name, byte[] buffer, int offset, int length, Encoding encoding) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return buffer == null ? throw new ArgumentNullException(nameof(buffer)) : GetNameBytes(name, 0, buffer, offset, length, encoding); + } + /// + /// Add a string to a buffer as a collection of ascii bytes. + /// + /// The string to add + /// The offset of the first character to add. + /// The buffer to add to. + /// The offset to start adding at. + /// The number of ascii characters to add. + /// The next free index in the buffer. + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length) + { + return GetAsciiBytes(toAdd, nameOffset, buffer, bufferOffset, length, null); + } + + /// + /// Add a string to a buffer as a collection of ascii bytes. + /// + /// The string to add + /// The offset of the first character to add. + /// The buffer to add to. + /// The offset to start adding at. + /// The number of ascii characters to add. + /// String encoding, or null for ASCII only + /// The next free index in the buffer. + public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length, Encoding encoding) + { + if (toAdd == null) + { + throw new ArgumentNullException(nameof(toAdd)); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + int i; + if (encoding == null) + { + for (i = 0; i < length && nameOffset + i < toAdd.Length; ++i) + { + buffer[bufferOffset + i] = (byte)toAdd[nameOffset + i]; + } + } + else + { + // It can be more sufficient if using unsafe code or Span(ToCharArray can be omitted) + var chars = toAdd.ToCharArray(); + // It can be more sufficient if using Span(or unsafe?) and ArrayPool for temporary buffer + var bytes = encoding.GetBytes(chars, nameOffset, Math.Min(toAdd.Length - nameOffset, length)); + i = Math.Min(bytes.Length, length); + Array.Copy(bytes, 0, buffer, bufferOffset, i); + } + // If length is beyond the toAdd string length (which is OK by the prev loop condition), eg if a field has fixed length and the string is shorter, make sure all of the extra chars are written as NULLs, so that the reader func would ignore them and get back the original string + for (; i < length; ++i) + buffer[bufferOffset + i] = 0; + return bufferOffset + length; + } + + /// + /// Put an octal representation of a value into a buffer + /// + /// + /// the value to be converted to octal + /// + /// + /// buffer to store the octal string + /// + /// + /// The offset into the buffer where the value starts + /// + /// + /// The length of the octal string to create + /// + /// + /// The offset of the character next byte after the octal string + /// + public static int GetOctalBytes(long value, byte[] buffer, int offset, int length) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + var localIndex = length - 1; + + // Either a space or null is valid here. We use NULL as per GNUTar + buffer[offset + localIndex] = 0; + --localIndex; + + if (value > 0) + { + for (var v = value; (localIndex >= 0) && (v > 0); --localIndex) + { + buffer[offset + localIndex] = (byte)((byte)'0' + (byte)(v & 7)); + v >>= 3; + } + } + + for (; localIndex >= 0; --localIndex) + { + buffer[offset + localIndex] = (byte)'0'; + } + + return offset + length; + } + + /// + /// Put an octal or binary representation of a value into a buffer + /// + /// Value to be convert to octal + /// The buffer to update + /// The offset into the buffer to store the value + /// The length of the octal string. Must be 12. + /// Index of next byte + private static int GetBinaryOrOctalBytes(long value, byte[] buffer, int offset, int length) + { + if (value > 0x1FFFFFFFF) + { // Octal 77777777777 (11 digits) + // Put value as binary, right-justified into the buffer. Set high order bit of left-most byte. + for (var pos = length - 1; pos > 0; pos--) + { + buffer[offset + pos] = (byte)value; + value >>= 8; + } + buffer[offset] = 0x80; + return offset + length; + } + return GetOctalBytes(value, buffer, offset, length); + } + + /// + /// Add the checksum integer to header buffer. + /// + /// + /// The header buffer to set the checksum for + /// The offset into the buffer for the checksum + /// The number of header bytes to update. + /// It's formatted differently from the other fields: it has 6 digits, a + /// null, then a space -- rather than digits, a space, then a null. + /// The final space is already there, from checksumming + /// + /// The modified buffer offset + private static void GetCheckSumOctalBytes(long value, byte[] buffer, int offset, int length) + { + GetOctalBytes(value, buffer, offset, length - 1); + } + + /// + /// Compute the checksum for a tar entry header. + /// The checksum field must be all spaces prior to this happening + /// + /// The tar entry's header buffer. + /// The computed checksum. + private static int ComputeCheckSum(byte[] buffer) + { + var sum = 0; + for (var i = 0; i < buffer.Length; ++i) + { + sum += buffer[i]; + } + return sum; + } + + /// + /// Make a checksum for a tar entry ignoring the checksum contents. + /// + /// The tar entry's header buffer. + /// The checksum for the buffer + private static int MakeCheckSum(byte[] buffer) + { + var sum = 0; + for (var i = 0; i < CHKSUMOFS; ++i) + { + sum += buffer[i]; + } + + for (var i = 0; i < CHKSUMLEN; ++i) + { + sum += (byte)' '; + } + + for (var i = CHKSUMOFS + CHKSUMLEN; i < buffer.Length; ++i) + { + sum += buffer[i]; + } + return sum; + } + + private static int GetCTime(DateTime dateTime) + { + return unchecked((int)((dateTime.Ticks - dateTime1970.Ticks) / timeConversionFactor)); + } + + private static DateTime GetDateTimeFromCTime(long ticks) + { + DateTime result; + + try + { + result = new DateTime(dateTime1970.Ticks + (ticks * timeConversionFactor)); + } + catch (ArgumentOutOfRangeException) + { + result = dateTime1970; + } + return result; + } + + #region Instance Fields + + private string name; + private int mode; + private int userId; + private int groupId; + private long size; + private DateTime modTime; + private int checksum; + private bool isChecksumValid; + private byte typeFlag; + private string linkName; + private string magic; + private string version; + private string userName; + private string groupName; + private int devMajor; + private int devMinor; + + #endregion Instance Fields + + #region Class Fields + + // Values used during recursive operations. + internal static int userIdAsSet; + + internal static int groupIdAsSet; + internal static string userNameAsSet; + internal static string groupNameAsSet = "None"; + + internal static int defaultUserId; + internal static int defaultGroupId; + internal static string defaultGroupName = "None"; + internal static string defaultUser; + + #endregion Class Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs index 26fbff706..34cc55532 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs @@ -2,770 +2,769 @@ using System.IO; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// The TarInputStream reads a UNIX tar archive as an InputStream. +/// methods are provided to position at each successive entry in +/// the archive, and the read each entry as a normal input stream +/// using read(). +/// +public class TarInputStream : Stream { - /// - /// The TarInputStream reads a UNIX tar archive as an InputStream. - /// methods are provided to position at each successive entry in - /// the archive, and the read each entry as a normal input stream - /// using read(). - /// - public class TarInputStream : Stream - { - #region Constructors - - /// - /// Construct a TarInputStream with default block factor - /// - /// stream to source data from - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public TarInputStream(Stream inputStream) - : this(inputStream, TarBuffer.DefaultBlockFactor, null) - { - } - /// - /// Construct a TarInputStream with default block factor - /// - /// stream to source data from - /// The used for the Name fields, or null for ASCII only - public TarInputStream(Stream inputStream, Encoding nameEncoding) - : this(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding) - { - } - - /// - /// Construct a TarInputStream with user specified block factor - /// - /// stream to source data from - /// block factor to apply to archive - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public TarInputStream(Stream inputStream, int blockFactor) - { - this.inputStream = inputStream; - tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor); - encoding = null; - } - - /// - /// Construct a TarInputStream with user specified block factor - /// - /// stream to source data from - /// block factor to apply to archive - /// The used for the Name fields, or null for ASCII only - public TarInputStream(Stream inputStream, int blockFactor, Encoding nameEncoding) - { - this.inputStream = inputStream; - tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor); - encoding = nameEncoding; - } - - #endregion Constructors - - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner - { - get { return tarBuffer.IsStreamOwner; } - set { tarBuffer.IsStreamOwner = value; } - } - - #region Stream Overrides - - /// - /// Gets a value indicating whether the current stream supports reading - /// - public override bool CanRead - { - get - { - return inputStream.CanRead; - } - } - - /// - /// Gets a value indicating whether the current stream supports seeking - /// This property always returns false. - /// - public override bool CanSeek - { - get - { - return false; - } - } - - /// - /// Gets a value indicating if the stream supports writing. - /// This property always returns false. - /// - public override bool CanWrite - { - get - { - return false; - } - } - - /// - /// The length in bytes of the stream - /// - public override long Length - { - get - { - return inputStream.Length; - } - } - - /// - /// Gets or sets the position within the stream. - /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException - /// - /// Any attempt to set position - public override long Position - { - get - { - return inputStream.Position; - } - set - { - throw new NotSupportedException("TarInputStream Seek not supported"); - } - } - - /// - /// Flushes the baseInputStream - /// - public override void Flush() - { - inputStream.Flush(); - } - - /// - /// Set the streams position. This operation is not supported and will throw a NotSupportedException - /// - /// The offset relative to the origin to seek to. - /// The to start seeking from. - /// The new position in the stream. - /// Any access - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("TarInputStream Seek not supported"); - } - - /// - /// Sets the length of the stream - /// This operation is not supported and will throw a NotSupportedException - /// - /// The new stream length. - /// Any access - public override void SetLength(long value) - { - throw new NotSupportedException("TarInputStream SetLength not supported"); - } - - /// - /// Writes a block of bytes to this stream using data from a buffer. - /// This operation is not supported and will throw a NotSupportedException - /// - /// The buffer containing bytes to write. - /// The offset in the buffer of the frist byte to write. - /// The number of bytes to write. - /// Any access - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("TarInputStream Write not supported"); - } - - /// - /// Writes a byte to the current position in the file stream. - /// This operation is not supported and will throw a NotSupportedException - /// - /// The byte value to write. - /// Any access - public override void WriteByte(byte value) - { - throw new NotSupportedException("TarInputStream WriteByte not supported"); - } - - /// - /// Reads a byte from the current tar archive entry. - /// - /// A byte cast to an int; -1 if the at the end of the stream. - public override int ReadByte() - { - byte[] oneByteBuffer = new byte[1]; - int num = Read(oneByteBuffer, 0, 1); - if (num <= 0) - { - // return -1 to indicate that no byte was read. - return -1; - } - return oneByteBuffer[0]; - } - - /// - /// Reads bytes from the current tar archive entry. - /// - /// This method is aware of the boundaries of the current - /// entry in the archive and will deal with them appropriately - /// - /// - /// The buffer into which to place bytes read. - /// - /// - /// The offset at which to place bytes read. - /// - /// - /// The number of bytes to read. - /// - /// - /// The number of bytes read, or 0 at end of stream/EOF. - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - int totalRead = 0; - - if (entryOffset >= entrySize) - { - return 0; - } - - long numToRead = count; - - if ((numToRead + entryOffset) > entrySize) - { - numToRead = entrySize - entryOffset; - } - - if (readBuffer != null) - { - int sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead; - - Array.Copy(readBuffer, 0, buffer, offset, sz); - - if (sz >= readBuffer.Length) - { - readBuffer = null; - } - else - { - int newLen = readBuffer.Length - sz; - byte[] newBuf = new byte[newLen]; - Array.Copy(readBuffer, sz, newBuf, 0, newLen); - readBuffer = newBuf; - } - - totalRead += sz; - numToRead -= sz; - offset += sz; - } - - while (numToRead > 0) - { - byte[] rec = tarBuffer.ReadBlock(); - if (rec == null) - { - // Unexpected EOF! - throw new TarException("unexpected EOF with " + numToRead + " bytes unread"); - } - - var sz = (int)numToRead; - int recLen = rec.Length; - - if (recLen > sz) - { - Array.Copy(rec, 0, buffer, offset, sz); - readBuffer = new byte[recLen - sz]; - Array.Copy(rec, sz, readBuffer, 0, recLen - sz); - } - else - { - sz = recLen; - Array.Copy(rec, 0, buffer, offset, recLen); - } - - totalRead += sz; - numToRead -= sz; - offset += sz; - } - - entryOffset += totalRead; - - return totalRead; - } - - /// - /// Closes this stream. Calls the TarBuffer's close() method. - /// The underlying stream is closed by the TarBuffer. - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - tarBuffer.Close(); - } - } - - #endregion Stream Overrides - - /// - /// Set the entry factory for this instance. - /// - /// The factory for creating new entries - public void SetEntryFactory(IEntryFactory factory) - { - entryFactory = factory; - } - - /// - /// Get the record size being used by this stream's TarBuffer. - /// - public int RecordSize - { - get { return tarBuffer.RecordSize; } - } - - /// - /// Get the record size being used by this stream's TarBuffer. - /// - /// - /// TarBuffer record size. - /// - [Obsolete("Use RecordSize property instead")] - public int GetRecordSize() - { - return tarBuffer.RecordSize; - } - - /// - /// Get the available data that can be read from the current - /// entry in the archive. This does not indicate how much data - /// is left in the entire archive, only in the current entry. - /// This value is determined from the entry's size header field - /// and the amount of data already read from the current entry. - /// - /// - /// The number of available bytes for the current entry. - /// - public long Available - { - get - { - return entrySize - entryOffset; - } - } - - /// - /// Skip bytes in the input buffer. This skips bytes in the - /// current entry's data, not the entire archive, and will - /// stop at the end of the current entry's data if the number - /// to skip extends beyond that point. - /// - /// - /// The number of bytes to skip. - /// - public void Skip(long skipCount) - { - // TODO: REVIEW efficiency of TarInputStream.Skip - // This is horribly inefficient, but it ensures that we - // properly skip over bytes via the TarBuffer... - // - byte[] skipBuf = new byte[8 * 1024]; - - for (long num = skipCount; num > 0;) - { - int toRead = num > skipBuf.Length ? skipBuf.Length : (int)num; - int numRead = Read(skipBuf, 0, toRead); - - if (numRead == -1) - { - break; - } - - num -= numRead; - } - } - - /// - /// Return a value of true if marking is supported; false otherwise. - /// - /// Currently marking is not supported, the return value is always false. - public bool IsMarkSupported - { - get - { - return false; - } - } - - /// - /// Since we do not support marking just yet, we do nothing. - /// - /// - /// The limit to mark. - /// - public void Mark(int markLimit) - { - } - - /// - /// Since we do not support marking just yet, we do nothing. - /// - public void Reset() - { - } - - /// - /// Get the next entry in this tar archive. This will skip - /// over any remaining data in the current entry, if there - /// is one, and place the input stream at the header of the - /// next entry, and read the header and instantiate a new - /// TarEntry from the header bytes and return that entry. - /// If there are no more entries in the archive, null will - /// be returned to indicate that the end of the archive has - /// been reached. - /// - /// - /// The next TarEntry in the archive, or null. - /// - public TarEntry GetNextEntry() - { - if (hasHitEOF) - { - return null; - } - - if (currentEntry != null) - { - SkipToNextEntry(); - } - - byte[] headerBuf = tarBuffer.ReadBlock(); - - if (headerBuf == null) - { - hasHitEOF = true; - } - else if (TarBuffer.IsEndOfArchiveBlock(headerBuf)) - { - hasHitEOF = true; - - // Read the second zero-filled block - tarBuffer.ReadBlock(); - } - else - { - hasHitEOF = false; - } - - if (hasHitEOF) - { - currentEntry = null; - } - else - { - try - { - var header = new TarHeader(); - header.ParseBuffer(headerBuf, encoding); - if (!header.IsChecksumValid) - { - throw new TarException("Header checksum is invalid"); - } - this.entryOffset = 0; - this.entrySize = header.Size; - - StringBuilder longName = null; - - if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) - { - byte[] nameBuffer = new byte[TarBuffer.BlockSize]; - long numToRead = this.entrySize; - - longName = new StringBuilder(); - - while (numToRead > 0) - { - int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead)); - - if (numRead == -1) - { - throw new InvalidHeaderException("Failed to read long name entry"); - } - - longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead, encoding).ToString()); - numToRead -= numRead; - } - - SkipToNextEntry(); - headerBuf = this.tarBuffer.ReadBlock(); - } - else if (header.TypeFlag == TarHeader.LF_GHDR) - { // POSIX global extended header - // Ignore things we dont understand completely for now - SkipToNextEntry(); - headerBuf = this.tarBuffer.ReadBlock(); - } - else if (header.TypeFlag == TarHeader.LF_XHDR) - { // POSIX extended header - byte[] nameBuffer = new byte[TarBuffer.BlockSize]; - long numToRead = this.entrySize; - - var xhr = new TarExtendedHeaderReader(); - - while (numToRead > 0) - { - int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead)); - - if (numRead == -1) - { - throw new InvalidHeaderException("Failed to read long name entry"); - } - - xhr.Read(nameBuffer, numRead); - numToRead -= numRead; - } - - if (xhr.Headers.TryGetValue("path", out string name)) - { - longName = new StringBuilder(name); - } - - SkipToNextEntry(); - headerBuf = this.tarBuffer.ReadBlock(); - } - else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) - { - // TODO: could show volume name when verbose - SkipToNextEntry(); - headerBuf = this.tarBuffer.ReadBlock(); - } - else if (header.TypeFlag != TarHeader.LF_NORMAL && - header.TypeFlag != TarHeader.LF_OLDNORM && - header.TypeFlag != TarHeader.LF_LINK && - header.TypeFlag != TarHeader.LF_SYMLINK && - header.TypeFlag != TarHeader.LF_DIR) - { - // Ignore things we dont understand completely for now - SkipToNextEntry(); - headerBuf = tarBuffer.ReadBlock(); - } - - if (entryFactory == null) - { - currentEntry = new TarEntry(headerBuf, encoding); - if (longName != null) - { - currentEntry.Name = longName.ToString(); - } - } - else - { - currentEntry = entryFactory.CreateEntry(headerBuf); - } - - // Magic was checked here for 'ustar' but there are multiple valid possibilities - // so this is not done anymore. - - entryOffset = 0; - - // TODO: Review How do we resolve this discrepancy?! - entrySize = this.currentEntry.Size; - } - catch (InvalidHeaderException ex) - { - entrySize = 0; - entryOffset = 0; - currentEntry = null; - string errorText = string.Format("Bad header in record {0} block {1} {2}", - tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message); - throw new InvalidHeaderException(errorText); - } - } - return currentEntry; - } - - /// - /// Copies the contents of the current tar archive entry directly into - /// an output stream. - /// - /// - /// The OutputStream into which to write the entry's data. - /// - public void CopyEntryContents(Stream outputStream) - { - byte[] tempBuffer = new byte[32 * 1024]; - - while (true) - { - int numRead = Read(tempBuffer, 0, tempBuffer.Length); - if (numRead <= 0) - { - break; - } - outputStream.Write(tempBuffer, 0, numRead); - } - } - - private void SkipToNextEntry() - { - long numToSkip = entrySize - entryOffset; - - if (numToSkip > 0) - { - Skip(numToSkip); - } - - readBuffer = null; - } - - /// - /// This interface is provided, along with the method , to allow - /// the programmer to have their own subclass instantiated for the - /// entries return from . - /// - public interface IEntryFactory - { - // This interface does not considering name encoding. - // How this interface should be? - /// - /// Create an entry based on name alone - /// - /// - /// Name of the new EntryPointNotFoundException to create - /// - /// created TarEntry or descendant class - TarEntry CreateEntry(string name); - - /// - /// Create an instance based on an actual file - /// - /// - /// Name of file to represent in the entry - /// - /// - /// Created TarEntry or descendant class - /// - TarEntry CreateEntryFromFile(string fileName); - - /// - /// Create a tar entry based on the header information passed - /// - /// - /// Buffer containing header information to create an entry from. - /// - /// - /// Created TarEntry or descendant class - /// - TarEntry CreateEntry(byte[] headerBuffer); - } - - /// - /// Standard entry factory class creating instances of the class TarEntry - /// - public class EntryFactoryAdapter : IEntryFactory - { - Encoding nameEncoding; - /// - /// Construct standard entry factory class with ASCII name encoding - /// - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public EntryFactoryAdapter() - { - } - /// - /// Construct standard entry factory with name encoding - /// - /// The used for the Name fields, or null for ASCII only - public EntryFactoryAdapter(Encoding nameEncoding) - { - this.nameEncoding = nameEncoding; - } - /// - /// Create a based on named - /// - /// The name to use for the entry - /// A new - public TarEntry CreateEntry(string name) - { - return TarEntry.CreateTarEntry(name); - } - - /// - /// Create a tar entry with details obtained from file - /// - /// The name of the file to retrieve details from. - /// A new - public TarEntry CreateEntryFromFile(string fileName) - { - return TarEntry.CreateEntryFromFile(fileName); - } - - /// - /// Create an entry based on details in header - /// - /// The buffer containing entry details. - /// A new - public TarEntry CreateEntry(byte[] headerBuffer) - { - return new TarEntry(headerBuffer, nameEncoding); - } - } - - #region Instance Fields - - /// - /// Flag set when last block has been read - /// - protected bool hasHitEOF; - - /// - /// Size of this entry as recorded in header - /// - protected long entrySize; - - /// - /// Number of bytes read for this entry so far - /// - protected long entryOffset; - - /// - /// Buffer used with calls to Read() - /// - protected byte[] readBuffer; - - /// - /// Working buffer - /// - protected TarBuffer tarBuffer; - - /// - /// Current entry being read - /// - private TarEntry currentEntry; - - /// - /// Factory used to create TarEntry or descendant class instance - /// - protected IEntryFactory entryFactory; - - /// - /// Stream used as the source of input data. - /// - private readonly Stream inputStream; - - private readonly Encoding encoding; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Construct a TarInputStream with default block factor + /// + /// stream to source data from + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public TarInputStream(Stream inputStream) + : this(inputStream, TarBuffer.DefaultBlockFactor, null) + { + } + /// + /// Construct a TarInputStream with default block factor + /// + /// stream to source data from + /// The used for the Name fields, or null for ASCII only + public TarInputStream(Stream inputStream, Encoding nameEncoding) + : this(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding) + { + } + + /// + /// Construct a TarInputStream with user specified block factor + /// + /// stream to source data from + /// block factor to apply to archive + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public TarInputStream(Stream inputStream, int blockFactor) + { + this.inputStream = inputStream; + tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor); + encoding = null; + } + + /// + /// Construct a TarInputStream with user specified block factor + /// + /// stream to source data from + /// block factor to apply to archive + /// The used for the Name fields, or null for ASCII only + public TarInputStream(Stream inputStream, int blockFactor, Encoding nameEncoding) + { + this.inputStream = inputStream; + tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor); + encoding = nameEncoding; + } + + #endregion Constructors + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner + { + get { return tarBuffer.IsStreamOwner; } + set { tarBuffer.IsStreamOwner = value; } + } + + #region Stream Overrides + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return inputStream.CanRead; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking + /// This property always returns false. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value indicating if the stream supports writing. + /// This property always returns false. + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// The length in bytes of the stream + /// + public override long Length + { + get + { + return inputStream.Length; + } + } + + /// + /// Gets or sets the position within the stream. + /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException + /// + /// Any attempt to set position + public override long Position + { + get + { + return inputStream.Position; + } + set + { + throw new NotSupportedException("TarInputStream Seek not supported"); + } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + inputStream.Flush(); + } + + /// + /// Set the streams position. This operation is not supported and will throw a NotSupportedException + /// + /// The offset relative to the origin to seek to. + /// The to start seeking from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("TarInputStream Seek not supported"); + } + + /// + /// Sets the length of the stream + /// This operation is not supported and will throw a NotSupportedException + /// + /// The new stream length. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("TarInputStream SetLength not supported"); + } + + /// + /// Writes a block of bytes to this stream using data from a buffer. + /// This operation is not supported and will throw a NotSupportedException + /// + /// The buffer containing bytes to write. + /// The offset in the buffer of the frist byte to write. + /// The number of bytes to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("TarInputStream Write not supported"); + } + + /// + /// Writes a byte to the current position in the file stream. + /// This operation is not supported and will throw a NotSupportedException + /// + /// The byte value to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("TarInputStream WriteByte not supported"); + } + + /// + /// Reads a byte from the current tar archive entry. + /// + /// A byte cast to an int; -1 if the at the end of the stream. + public override int ReadByte() + { + var oneByteBuffer = new byte[1]; + var num = Read(oneByteBuffer, 0, 1); + if (num <= 0) + { + // return -1 to indicate that no byte was read. + return -1; + } + return oneByteBuffer[0]; + } + + /// + /// Reads bytes from the current tar archive entry. + /// + /// This method is aware of the boundaries of the current + /// entry in the archive and will deal with them appropriately + /// + /// + /// The buffer into which to place bytes read. + /// + /// + /// The offset at which to place bytes read. + /// + /// + /// The number of bytes to read. + /// + /// + /// The number of bytes read, or 0 at end of stream/EOF. + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + var totalRead = 0; + + if (entryOffset >= entrySize) + { + return 0; + } + + long numToRead = count; + + if ((numToRead + entryOffset) > entrySize) + { + numToRead = entrySize - entryOffset; + } + + if (readBuffer != null) + { + var sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead; + + Array.Copy(readBuffer, 0, buffer, offset, sz); + + if (sz >= readBuffer.Length) + { + readBuffer = null; + } + else + { + var newLen = readBuffer.Length - sz; + var newBuf = new byte[newLen]; + Array.Copy(readBuffer, sz, newBuf, 0, newLen); + readBuffer = newBuf; + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + while (numToRead > 0) + { + var rec = tarBuffer.ReadBlock(); + if (rec == null) + { + // Unexpected EOF! + throw new TarException("unexpected EOF with " + numToRead + " bytes unread"); + } + + var sz = (int)numToRead; + var recLen = rec.Length; + + if (recLen > sz) + { + Array.Copy(rec, 0, buffer, offset, sz); + readBuffer = new byte[recLen - sz]; + Array.Copy(rec, sz, readBuffer, 0, recLen - sz); + } + else + { + sz = recLen; + Array.Copy(rec, 0, buffer, offset, recLen); + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + entryOffset += totalRead; + + return totalRead; + } + + /// + /// Closes this stream. Calls the TarBuffer's close() method. + /// The underlying stream is closed by the TarBuffer. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + tarBuffer.Close(); + } + } + + #endregion Stream Overrides + + /// + /// Set the entry factory for this instance. + /// + /// The factory for creating new entries + public void SetEntryFactory(IEntryFactory factory) + { + entryFactory = factory; + } + + /// + /// Get the record size being used by this stream's TarBuffer. + /// + public int RecordSize + { + get { return tarBuffer.RecordSize; } + } + + /// + /// Get the record size being used by this stream's TarBuffer. + /// + /// + /// TarBuffer record size. + /// + [Obsolete("Use RecordSize property instead")] + public int GetRecordSize() + { + return tarBuffer.RecordSize; + } + + /// + /// Get the available data that can be read from the current + /// entry in the archive. This does not indicate how much data + /// is left in the entire archive, only in the current entry. + /// This value is determined from the entry's size header field + /// and the amount of data already read from the current entry. + /// + /// + /// The number of available bytes for the current entry. + /// + public long Available + { + get + { + return entrySize - entryOffset; + } + } + + /// + /// Skip bytes in the input buffer. This skips bytes in the + /// current entry's data, not the entire archive, and will + /// stop at the end of the current entry's data if the number + /// to skip extends beyond that point. + /// + /// + /// The number of bytes to skip. + /// + public void Skip(long skipCount) + { + // TODO: REVIEW efficiency of TarInputStream.Skip + // This is horribly inefficient, but it ensures that we + // properly skip over bytes via the TarBuffer... + // + var skipBuf = new byte[8 * 1024]; + + for (var num = skipCount; num > 0;) + { + var toRead = num > skipBuf.Length ? skipBuf.Length : (int)num; + var numRead = Read(skipBuf, 0, toRead); + + if (numRead == -1) + { + break; + } + + num -= numRead; + } + } + + /// + /// Return a value of true if marking is supported; false otherwise. + /// + /// Currently marking is not supported, the return value is always false. + public bool IsMarkSupported + { + get + { + return false; + } + } + + /// + /// Since we do not support marking just yet, we do nothing. + /// + /// + /// The limit to mark. + /// + public void Mark(int markLimit) + { + } + + /// + /// Since we do not support marking just yet, we do nothing. + /// + public void Reset() + { + } + + /// + /// Get the next entry in this tar archive. This will skip + /// over any remaining data in the current entry, if there + /// is one, and place the input stream at the header of the + /// next entry, and read the header and instantiate a new + /// TarEntry from the header bytes and return that entry. + /// If there are no more entries in the archive, null will + /// be returned to indicate that the end of the archive has + /// been reached. + /// + /// + /// The next TarEntry in the archive, or null. + /// + public TarEntry GetNextEntry() + { + if (hasHitEOF) + { + return null; + } + + if (currentEntry != null) + { + SkipToNextEntry(); + } + + var headerBuf = tarBuffer.ReadBlock(); + + if (headerBuf == null) + { + hasHitEOF = true; + } + else if (TarBuffer.IsEndOfArchiveBlock(headerBuf)) + { + hasHitEOF = true; + + // Read the second zero-filled block + tarBuffer.ReadBlock(); + } + else + { + hasHitEOF = false; + } + + if (hasHitEOF) + { + currentEntry = null; + } + else + { + try + { + var header = new TarHeader(); + header.ParseBuffer(headerBuf, encoding); + if (!header.IsChecksumValid) + { + throw new TarException("Header checksum is invalid"); + } + this.entryOffset = 0; + this.entrySize = header.Size; + + StringBuilder longName = null; + + if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) + { + var nameBuffer = new byte[TarBuffer.BlockSize]; + var numToRead = this.entrySize; + + longName = new StringBuilder(); + + while (numToRead > 0) + { + var numRead = this.Read(nameBuffer, 0, numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead); + + if (numRead == -1) + { + throw new InvalidHeaderException("Failed to read long name entry"); + } + + longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead, encoding).ToString()); + numToRead -= numRead; + } + + SkipToNextEntry(); + headerBuf = this.tarBuffer.ReadBlock(); + } + else if (header.TypeFlag == TarHeader.LF_GHDR) + { // POSIX global extended header + // Ignore things we dont understand completely for now + SkipToNextEntry(); + headerBuf = this.tarBuffer.ReadBlock(); + } + else if (header.TypeFlag == TarHeader.LF_XHDR) + { // POSIX extended header + var nameBuffer = new byte[TarBuffer.BlockSize]; + var numToRead = this.entrySize; + + var xhr = new TarExtendedHeaderReader(); + + while (numToRead > 0) + { + var numRead = this.Read(nameBuffer, 0, numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead); + + if (numRead == -1) + { + throw new InvalidHeaderException("Failed to read long name entry"); + } + + xhr.Read(nameBuffer, numRead); + numToRead -= numRead; + } + + if (xhr.Headers.TryGetValue("path", out var name)) + { + longName = new StringBuilder(name); + } + + SkipToNextEntry(); + headerBuf = this.tarBuffer.ReadBlock(); + } + else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) + { + // TODO: could show volume name when verbose + SkipToNextEntry(); + headerBuf = this.tarBuffer.ReadBlock(); + } + else if (header.TypeFlag is not TarHeader.LF_NORMAL and + not TarHeader.LF_OLDNORM and + not TarHeader.LF_LINK and + not TarHeader.LF_SYMLINK and + not TarHeader.LF_DIR) + { + // Ignore things we dont understand completely for now + SkipToNextEntry(); + headerBuf = tarBuffer.ReadBlock(); + } + + if (entryFactory == null) + { + currentEntry = new TarEntry(headerBuf, encoding); + if (longName != null) + { + currentEntry.Name = longName.ToString(); + } + } + else + { + currentEntry = entryFactory.CreateEntry(headerBuf); + } + + // Magic was checked here for 'ustar' but there are multiple valid possibilities + // so this is not done anymore. + + entryOffset = 0; + + // TODO: Review How do we resolve this discrepancy?! + entrySize = this.currentEntry.Size; + } + catch (InvalidHeaderException ex) + { + entrySize = 0; + entryOffset = 0; + currentEntry = null; + var errorText = string.Format("Bad header in record {0} block {1} {2}", + tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message); + throw new InvalidHeaderException(errorText); + } + } + return currentEntry; + } + + /// + /// Copies the contents of the current tar archive entry directly into + /// an output stream. + /// + /// + /// The OutputStream into which to write the entry's data. + /// + public void CopyEntryContents(Stream outputStream) + { + var tempBuffer = new byte[32 * 1024]; + + while (true) + { + var numRead = Read(tempBuffer, 0, tempBuffer.Length); + if (numRead <= 0) + { + break; + } + outputStream.Write(tempBuffer, 0, numRead); + } + } + + private void SkipToNextEntry() + { + var numToSkip = entrySize - entryOffset; + + if (numToSkip > 0) + { + Skip(numToSkip); + } + + readBuffer = null; + } + + /// + /// This interface is provided, along with the method , to allow + /// the programmer to have their own subclass instantiated for the + /// entries return from . + /// + public interface IEntryFactory + { + // This interface does not considering name encoding. + // How this interface should be? + /// + /// Create an entry based on name alone + /// + /// + /// Name of the new EntryPointNotFoundException to create + /// + /// created TarEntry or descendant class + TarEntry CreateEntry(string name); + + /// + /// Create an instance based on an actual file + /// + /// + /// Name of file to represent in the entry + /// + /// + /// Created TarEntry or descendant class + /// + TarEntry CreateEntryFromFile(string fileName); + + /// + /// Create a tar entry based on the header information passed + /// + /// + /// Buffer containing header information to create an entry from. + /// + /// + /// Created TarEntry or descendant class + /// + TarEntry CreateEntry(byte[] headerBuffer); + } + + /// + /// Standard entry factory class creating instances of the class TarEntry + /// + public class EntryFactoryAdapter : IEntryFactory + { + private readonly Encoding nameEncoding; + /// + /// Construct standard entry factory class with ASCII name encoding + /// + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public EntryFactoryAdapter() + { + } + /// + /// Construct standard entry factory with name encoding + /// + /// The used for the Name fields, or null for ASCII only + public EntryFactoryAdapter(Encoding nameEncoding) + { + this.nameEncoding = nameEncoding; + } + /// + /// Create a based on named + /// + /// The name to use for the entry + /// A new + public TarEntry CreateEntry(string name) + { + return TarEntry.CreateTarEntry(name); + } + + /// + /// Create a tar entry with details obtained from file + /// + /// The name of the file to retrieve details from. + /// A new + public TarEntry CreateEntryFromFile(string fileName) + { + return TarEntry.CreateEntryFromFile(fileName); + } + + /// + /// Create an entry based on details in header + /// + /// The buffer containing entry details. + /// A new + public TarEntry CreateEntry(byte[] headerBuffer) + { + return new TarEntry(headerBuffer, nameEncoding); + } + } + + #region Instance Fields + + /// + /// Flag set when last block has been read + /// + protected bool hasHitEOF; + + /// + /// Size of this entry as recorded in header + /// + protected long entrySize; + + /// + /// Number of bytes read for this entry so far + /// + protected long entryOffset; + + /// + /// Buffer used with calls to Read() + /// + protected byte[] readBuffer; + + /// + /// Working buffer + /// + protected TarBuffer tarBuffer; + + /// + /// Current entry being read + /// + private TarEntry currentEntry; + + /// + /// Factory used to create TarEntry or descendant class instance + /// + protected IEntryFactory entryFactory; + + /// + /// Stream used as the source of input data. + /// + private readonly Stream inputStream; + + private readonly Encoding encoding; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs index bf3a85280..0e874706a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs @@ -2,521 +2,512 @@ using System.IO; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.Tar +namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; + +/// +/// The TarOutputStream writes a UNIX tar archive as an OutputStream. +/// Methods are provided to put entries, and then write their contents +/// by writing to this stream using write(). +/// +/// public +public class TarOutputStream : Stream { - /// - /// The TarOutputStream writes a UNIX tar archive as an OutputStream. - /// Methods are provided to put entries, and then write their contents - /// by writing to this stream using write(). - /// - /// public - public class TarOutputStream : Stream - { - #region Constructors - - /// - /// Construct TarOutputStream using default block factor - /// - /// stream to write to - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public TarOutputStream(Stream outputStream) - : this(outputStream, TarBuffer.DefaultBlockFactor) - { - } - - /// - /// Construct TarOutputStream using default block factor - /// - /// stream to write to - /// The used for the Name fields, or null for ASCII only - public TarOutputStream(Stream outputStream, Encoding nameEncoding) - : this(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding) - { - } - - /// - /// Construct TarOutputStream with user specified block factor - /// - /// stream to write to - /// blocking factor - [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] - public TarOutputStream(Stream outputStream, int blockFactor) - { - if (outputStream == null) - { - throw new ArgumentNullException(nameof(outputStream)); - } - - this.outputStream = outputStream; - buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor); - - assemblyBuffer = new byte[TarBuffer.BlockSize]; - blockBuffer = new byte[TarBuffer.BlockSize]; - } - - /// - /// Construct TarOutputStream with user specified block factor - /// - /// stream to write to - /// blocking factor - /// The used for the Name fields, or null for ASCII only - public TarOutputStream(Stream outputStream, int blockFactor, Encoding nameEncoding) - { - if (outputStream == null) - { - throw new ArgumentNullException(nameof(outputStream)); - } - - this.outputStream = outputStream; - buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor); - - assemblyBuffer = new byte[TarBuffer.BlockSize]; - blockBuffer = new byte[TarBuffer.BlockSize]; - - this.nameEncoding = nameEncoding; - } - - #endregion Constructors - - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner - { - get { return buffer.IsStreamOwner; } - set { buffer.IsStreamOwner = value; } - } - - /// - /// true if the stream supports reading; otherwise, false. - /// - public override bool CanRead - { - get - { - return outputStream.CanRead; - } - } - - /// - /// true if the stream supports seeking; otherwise, false. - /// - public override bool CanSeek - { - get - { - return outputStream.CanSeek; - } - } - - /// - /// true if stream supports writing; otherwise, false. - /// - public override bool CanWrite - { - get - { - return outputStream.CanWrite; - } - } - - /// - /// length of stream in bytes - /// - public override long Length - { - get - { - return outputStream.Length; - } - } - - /// - /// gets or sets the position within the current stream. - /// - public override long Position - { - get - { - return outputStream.Position; - } - set - { - outputStream.Position = value; - } - } - - /// - /// set the position within the current stream - /// - /// The offset relative to the to seek to - /// The to seek from. - /// The new position in the stream. - public override long Seek(long offset, SeekOrigin origin) - { - return outputStream.Seek(offset, origin); - } - - /// - /// Set the length of the current stream - /// - /// The new stream length. - public override void SetLength(long value) - { - outputStream.SetLength(value); - } - - /// - /// Read a byte from the stream and advance the position within the stream - /// by one byte or returns -1 if at the end of the stream. - /// - /// The byte value or -1 if at end of stream - public override int ReadByte() - { - return outputStream.ReadByte(); - } - - /// - /// read bytes from the current stream and advance the position within the - /// stream by the number of bytes read. - /// - /// The buffer to store read bytes in. - /// The index into the buffer to being storing bytes at. - /// The desired number of bytes to read. - /// The total number of bytes read, or zero if at the end of the stream. - /// The number of bytes may be less than the count - /// requested if data is not available. - public override int Read(byte[] buffer, int offset, int count) - { - return outputStream.Read(buffer, offset, count); - } - - /// - /// All buffered data is written to destination - /// - public override void Flush() - { - outputStream.Flush(); - } - - /// - /// Ends the TAR archive without closing the underlying OutputStream. - /// The result is that the EOF block of nulls is written. - /// - public void Finish() - { - if (IsEntryOpen) - { - CloseEntry(); - } - WriteEofBlock(); - } - - /// - /// Ends the TAR archive and closes the underlying OutputStream. - /// - /// This means that Finish() is called followed by calling the - /// TarBuffer's Close(). - protected override void Dispose(bool disposing) - { - if (!isClosed) - { - isClosed = true; - Finish(); - buffer.Close(); - } - } - - /// - /// Get the record size being used by this stream's TarBuffer. - /// - public int RecordSize - { - get { return buffer.RecordSize; } - } - - /// - /// Get the record size being used by this stream's TarBuffer. - /// - /// - /// The TarBuffer record size. - /// - [Obsolete("Use RecordSize property instead")] - public int GetRecordSize() - { - return buffer.RecordSize; - } - - /// - /// Get a value indicating whether an entry is open, requiring more data to be written. - /// - private bool IsEntryOpen - { - get { return (currBytes < currSize); } - } - - /// - /// Put an entry on the output stream. This writes the entry's - /// header and positions the output stream for writing - /// the contents of the entry. Once this method is called, the - /// stream is ready for calls to write() to write the entry's - /// contents. Once the contents are written, closeEntry() - /// MUST be called to ensure that all buffered data - /// is completely written to the output stream. - /// - /// - /// The TarEntry to be written to the archive. - /// - public void PutNextEntry(TarEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - var namelen = nameEncoding != null ? nameEncoding.GetByteCount(entry.TarHeader.Name) : entry.TarHeader.Name.Length; - - if (namelen > TarHeader.NAMELEN) - { - var longHeader = new TarHeader(); - longHeader.TypeFlag = TarHeader.LF_GNU_LONGNAME; - longHeader.Name = longHeader.Name + "././@LongLink"; - longHeader.Mode = 420;//644 by default - longHeader.UserId = entry.UserId; - longHeader.GroupId = entry.GroupId; - longHeader.GroupName = entry.GroupName; - longHeader.UserName = entry.UserName; - longHeader.LinkName = ""; - longHeader.Size = namelen + 1; // Plus one to avoid dropping last char - - longHeader.WriteHeader(blockBuffer, nameEncoding); - buffer.WriteBlock(blockBuffer); // Add special long filename header block - - int nameCharIndex = 0; - - while (nameCharIndex < namelen + 1 /* we've allocated one for the null char, now we must make sure it gets written out */) - { - Array.Clear(blockBuffer, 0, blockBuffer.Length); - TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize, nameEncoding); // This func handles OK the extra char out of string length - nameCharIndex += TarBuffer.BlockSize; - buffer.WriteBlock(blockBuffer); - } - } - - entry.WriteEntryHeader(blockBuffer, nameEncoding); - buffer.WriteBlock(blockBuffer); - - currBytes = 0; - - currSize = entry.IsDirectory ? 0 : entry.Size; - } - - /// - /// Close an entry. This method MUST be called for all file - /// entries that contain data. The reason is that we must - /// buffer data written to the stream in order to satisfy - /// the buffer's block based writes. Thus, there may be - /// data fragments still being assembled that must be written - /// to the output stream before this entry is closed and the - /// next entry written. - /// - public void CloseEntry() - { - if (assemblyBufferLength > 0) - { - Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength); - - buffer.WriteBlock(assemblyBuffer); - - currBytes += assemblyBufferLength; - assemblyBufferLength = 0; - } - - if (currBytes < currSize) - { - string errorText = string.Format( - "Entry closed at '{0}' before the '{1}' bytes specified in the header were written", - currBytes, currSize); - throw new TarException(errorText); - } - } - - /// - /// Writes a byte to the current tar archive entry. - /// This method simply calls Write(byte[], int, int). - /// - /// - /// The byte to be written. - /// - public override void WriteByte(byte value) - { - Write(new byte[] { value }, 0, 1); - } - - /// - /// Writes bytes to the current tar archive entry. This method - /// is aware of the current entry and will throw an exception if - /// you attempt to write bytes past the length specified for the - /// current entry. The method is also (painfully) aware of the - /// record buffering required by TarBuffer, and manages buffers - /// that are not a multiple of recordsize in length, including - /// assembling records from small buffers. - /// - /// - /// The buffer to write to the archive. - /// - /// - /// The offset in the buffer from which to get bytes. - /// - /// - /// The number of bytes to write. - /// - public override void Write(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); - } - - if (buffer.Length - offset < count) - { - throw new ArgumentException("offset and count combination is invalid"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); - } - - if ((currBytes + count) > currSize) - { - string errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes", - count, this.currSize); - throw new ArgumentOutOfRangeException(nameof(count), errorText); - } - - // - // We have to deal with assembly!!! - // The programmer can be writing little 32 byte chunks for all - // we know, and we must assemble complete blocks for writing. - // TODO REVIEW Maybe this should be in TarBuffer? Could that help to - // eliminate some of the buffer copying. - // - if (assemblyBufferLength > 0) - { - if ((assemblyBufferLength + count) >= blockBuffer.Length) - { - int aLen = blockBuffer.Length - assemblyBufferLength; - - Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength); - Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen); - - this.buffer.WriteBlock(blockBuffer); - - currBytes += blockBuffer.Length; - - offset += aLen; - count -= aLen; - - assemblyBufferLength = 0; - } - else - { - Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); - offset += count; - assemblyBufferLength += count; - count -= count; - } - } - - // - // When we get here we have EITHER: - // o An empty "assembly" buffer. - // o No bytes to write (count == 0) - // - while (count > 0) - { - if (count < blockBuffer.Length) - { - Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); - assemblyBufferLength += count; - break; - } - - this.buffer.WriteBlock(buffer, offset); - - int bufferLength = blockBuffer.Length; - currBytes += bufferLength; - count -= bufferLength; - offset += bufferLength; - } - } - - /// - /// Write an EOF (end of archive) block to the tar archive. - /// The end of the archive is indicated by two blocks consisting entirely of zero bytes. - /// - private void WriteEofBlock() - { - Array.Clear(blockBuffer, 0, blockBuffer.Length); - buffer.WriteBlock(blockBuffer); - buffer.WriteBlock(blockBuffer); - } - - #region Instance Fields - - /// - /// bytes written for this entry so far - /// - private long currBytes; - - /// - /// current 'Assembly' buffer length - /// - private int assemblyBufferLength; - - /// - /// Flag indicating whether this instance has been closed or not. - /// - private bool isClosed; - - /// - /// Size for the current entry - /// - protected long currSize; - - /// - /// single block working buffer - /// - protected byte[] blockBuffer; - - /// - /// 'Assembly' buffer used to assemble data before writing - /// - protected byte[] assemblyBuffer; - - /// - /// TarBuffer used to provide correct blocking factor - /// - protected TarBuffer buffer; - - /// - /// the destination stream for the archive contents - /// - protected Stream outputStream; - - /// - /// name encoding - /// - protected Encoding nameEncoding; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Construct TarOutputStream using default block factor + /// + /// stream to write to + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public TarOutputStream(Stream outputStream) + : this(outputStream, TarBuffer.DefaultBlockFactor) + { + } + + /// + /// Construct TarOutputStream using default block factor + /// + /// stream to write to + /// The used for the Name fields, or null for ASCII only + public TarOutputStream(Stream outputStream, Encoding nameEncoding) + : this(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding) + { + } + + /// + /// Construct TarOutputStream with user specified block factor + /// + /// stream to write to + /// blocking factor + [Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")] + public TarOutputStream(Stream outputStream, int blockFactor) + { + this.outputStream = outputStream ?? throw new ArgumentNullException(nameof(outputStream)); + buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor); + + assemblyBuffer = new byte[TarBuffer.BlockSize]; + blockBuffer = new byte[TarBuffer.BlockSize]; + } + + /// + /// Construct TarOutputStream with user specified block factor + /// + /// stream to write to + /// blocking factor + /// The used for the Name fields, or null for ASCII only + public TarOutputStream(Stream outputStream, int blockFactor, Encoding nameEncoding) + { + this.outputStream = outputStream ?? throw new ArgumentNullException(nameof(outputStream)); + buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor); + + assemblyBuffer = new byte[TarBuffer.BlockSize]; + blockBuffer = new byte[TarBuffer.BlockSize]; + + this.nameEncoding = nameEncoding; + } + + #endregion Constructors + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner + { + get { return buffer.IsStreamOwner; } + set { buffer.IsStreamOwner = value; } + } + + /// + /// true if the stream supports reading; otherwise, false. + /// + public override bool CanRead + { + get + { + return outputStream.CanRead; + } + } + + /// + /// true if the stream supports seeking; otherwise, false. + /// + public override bool CanSeek + { + get + { + return outputStream.CanSeek; + } + } + + /// + /// true if stream supports writing; otherwise, false. + /// + public override bool CanWrite + { + get + { + return outputStream.CanWrite; + } + } + + /// + /// length of stream in bytes + /// + public override long Length + { + get + { + return outputStream.Length; + } + } + + /// + /// gets or sets the position within the current stream. + /// + public override long Position + { + get + { + return outputStream.Position; + } + set + { + outputStream.Position = value; + } + } + + /// + /// set the position within the current stream + /// + /// The offset relative to the to seek to + /// The to seek from. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + return outputStream.Seek(offset, origin); + } + + /// + /// Set the length of the current stream + /// + /// The new stream length. + public override void SetLength(long value) + { + outputStream.SetLength(value); + } + + /// + /// Read a byte from the stream and advance the position within the stream + /// by one byte or returns -1 if at the end of the stream. + /// + /// The byte value or -1 if at end of stream + public override int ReadByte() + { + return outputStream.ReadByte(); + } + + /// + /// read bytes from the current stream and advance the position within the + /// stream by the number of bytes read. + /// + /// The buffer to store read bytes in. + /// The index into the buffer to being storing bytes at. + /// The desired number of bytes to read. + /// The total number of bytes read, or zero if at the end of the stream. + /// The number of bytes may be less than the count + /// requested if data is not available. + public override int Read(byte[] buffer, int offset, int count) + { + return outputStream.Read(buffer, offset, count); + } + + /// + /// All buffered data is written to destination + /// + public override void Flush() + { + outputStream.Flush(); + } + + /// + /// Ends the TAR archive without closing the underlying OutputStream. + /// The result is that the EOF block of nulls is written. + /// + public void Finish() + { + if (IsEntryOpen) + { + CloseEntry(); + } + WriteEofBlock(); + } + + /// + /// Ends the TAR archive and closes the underlying OutputStream. + /// + /// This means that Finish() is called followed by calling the + /// TarBuffer's Close(). + protected override void Dispose(bool disposing) + { + if (!isClosed) + { + isClosed = true; + Finish(); + buffer.Close(); + } + } + + /// + /// Get the record size being used by this stream's TarBuffer. + /// + public int RecordSize + { + get { return buffer.RecordSize; } + } + + /// + /// Get the record size being used by this stream's TarBuffer. + /// + /// + /// The TarBuffer record size. + /// + [Obsolete("Use RecordSize property instead")] + public int GetRecordSize() + { + return buffer.RecordSize; + } + + /// + /// Get a value indicating whether an entry is open, requiring more data to be written. + /// + private bool IsEntryOpen + { + get { return currBytes < currSize; } + } + + /// + /// Put an entry on the output stream. This writes the entry's + /// header and positions the output stream for writing + /// the contents of the entry. Once this method is called, the + /// stream is ready for calls to write() to write the entry's + /// contents. Once the contents are written, closeEntry() + /// MUST be called to ensure that all buffered data + /// is completely written to the output stream. + /// + /// + /// The TarEntry to be written to the archive. + /// + public void PutNextEntry(TarEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + var namelen = nameEncoding != null ? nameEncoding.GetByteCount(entry.TarHeader.Name) : entry.TarHeader.Name.Length; + + if (namelen > TarHeader.NAMELEN) + { + var longHeader = new TarHeader + { + TypeFlag = TarHeader.LF_GNU_LONGNAME + }; + longHeader.Name += "././@LongLink"; + longHeader.Mode = 420;//644 by default + longHeader.UserId = entry.UserId; + longHeader.GroupId = entry.GroupId; + longHeader.GroupName = entry.GroupName; + longHeader.UserName = entry.UserName; + longHeader.LinkName = ""; + longHeader.Size = namelen + 1; // Plus one to avoid dropping last char + + longHeader.WriteHeader(blockBuffer, nameEncoding); + buffer.WriteBlock(blockBuffer); // Add special long filename header block + + var nameCharIndex = 0; + + while (nameCharIndex < namelen + 1 /* we've allocated one for the null char, now we must make sure it gets written out */) + { + Array.Clear(blockBuffer, 0, blockBuffer.Length); + TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize, nameEncoding); // This func handles OK the extra char out of string length + nameCharIndex += TarBuffer.BlockSize; + buffer.WriteBlock(blockBuffer); + } + } + + entry.WriteEntryHeader(blockBuffer, nameEncoding); + buffer.WriteBlock(blockBuffer); + + currBytes = 0; + + currSize = entry.IsDirectory ? 0 : entry.Size; + } + + /// + /// Close an entry. This method MUST be called for all file + /// entries that contain data. The reason is that we must + /// buffer data written to the stream in order to satisfy + /// the buffer's block based writes. Thus, there may be + /// data fragments still being assembled that must be written + /// to the output stream before this entry is closed and the + /// next entry written. + /// + public void CloseEntry() + { + if (assemblyBufferLength > 0) + { + Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength); + + buffer.WriteBlock(assemblyBuffer); + + currBytes += assemblyBufferLength; + assemblyBufferLength = 0; + } + + if (currBytes < currSize) + { + var errorText = string.Format( + "Entry closed at '{0}' before the '{1}' bytes specified in the header were written", + currBytes, currSize); + throw new TarException(errorText); + } + } + + /// + /// Writes a byte to the current tar archive entry. + /// This method simply calls Write(byte[], int, int). + /// + /// + /// The byte to be written. + /// + public override void WriteByte(byte value) + { + Write(new byte[] { value }, 0, 1); + } + + /// + /// Writes bytes to the current tar archive entry. This method + /// is aware of the current entry and will throw an exception if + /// you attempt to write bytes past the length specified for the + /// current entry. The method is also (painfully) aware of the + /// record buffering required by TarBuffer, and manages buffers + /// that are not a multiple of recordsize in length, including + /// assembling records from small buffers. + /// + /// + /// The buffer to write to the archive. + /// + /// + /// The offset in the buffer from which to get bytes. + /// + /// + /// The number of bytes to write. + /// + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); + } + + if (buffer.Length - offset < count) + { + throw new ArgumentException("offset and count combination is invalid"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); + } + + if ((currBytes + count) > currSize) + { + var errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes", + count, this.currSize); + throw new ArgumentOutOfRangeException(nameof(count), errorText); + } + + // + // We have to deal with assembly!!! + // The programmer can be writing little 32 byte chunks for all + // we know, and we must assemble complete blocks for writing. + // TODO REVIEW Maybe this should be in TarBuffer? Could that help to + // eliminate some of the buffer copying. + // + if (assemblyBufferLength > 0) + { + if ((assemblyBufferLength + count) >= blockBuffer.Length) + { + var aLen = blockBuffer.Length - assemblyBufferLength; + + Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength); + Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen); + + this.buffer.WriteBlock(blockBuffer); + + currBytes += blockBuffer.Length; + + offset += aLen; + count -= aLen; + + assemblyBufferLength = 0; + } + else + { + Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); + offset += count; + assemblyBufferLength += count; + count -= count; + } + } + + // + // When we get here we have EITHER: + // o An empty "assembly" buffer. + // o No bytes to write (count == 0) + // + while (count > 0) + { + if (count < blockBuffer.Length) + { + Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); + assemblyBufferLength += count; + break; + } + + this.buffer.WriteBlock(buffer, offset); + + var bufferLength = blockBuffer.Length; + currBytes += bufferLength; + count -= bufferLength; + offset += bufferLength; + } + } + + /// + /// Write an EOF (end of archive) block to the tar archive. + /// The end of the archive is indicated by two blocks consisting entirely of zero bytes. + /// + private void WriteEofBlock() + { + Array.Clear(blockBuffer, 0, blockBuffer.Length); + buffer.WriteBlock(blockBuffer); + buffer.WriteBlock(blockBuffer); + } + + #region Instance Fields + + /// + /// bytes written for this entry so far + /// + private long currBytes; + + /// + /// current 'Assembly' buffer length + /// + private int assemblyBufferLength; + + /// + /// Flag indicating whether this instance has been closed or not. + /// + private bool isClosed; + + /// + /// Size for the current entry + /// + protected long currSize; + + /// + /// single block working buffer + /// + protected byte[] blockBuffer; + + /// + /// 'Assembly' buffer used to assemble data before writing + /// + protected byte[] assemblyBuffer; + + /// + /// TarBuffer used to provide correct blocking factor + /// + protected TarBuffer buffer; + + /// + /// the destination stream for the archive contents + /// + protected Stream outputStream; + + /// + /// name encoding + /// + protected Encoding nameEncoding; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs index 2df353d20..3d19b89da 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs @@ -1,604 +1,603 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// This is the Deflater class. The deflater class compresses input +/// with the deflate algorithm described in RFC 1951. It has several +/// compression levels and three different strategies described below. +/// +/// This class is not thread safe. This is inherent in the API, due +/// to the split of deflate and setInput. +/// +/// author of the original java version : Jochen Hoenicke +/// +public class Deflater { - /// - /// This is the Deflater class. The deflater class compresses input - /// with the deflate algorithm described in RFC 1951. It has several - /// compression levels and three different strategies described below. - /// - /// This class is not thread safe. This is inherent in the API, due - /// to the split of deflate and setInput. - /// - /// author of the original java version : Jochen Hoenicke - /// - public class Deflater - { - #region Deflater Documentation - - /* - * The Deflater can do the following state transitions: - * - * (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---. - * / | (2) (5) | - * / v (5) | - * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) - * \ | (3) | ,--------' - * | | | (3) / - * v v (5) v v - * (1) -> BUSY_STATE ----> FINISHING_STATE - * | (6) - * v - * FINISHED_STATE - * \_____________________________________/ - * | (7) - * v - * CLOSED_STATE - * - * (1) If we should produce a header we start in INIT_STATE, otherwise - * we start in BUSY_STATE. - * (2) A dictionary may be set only when we are in INIT_STATE, then - * we change the state as indicated. - * (3) Whether a dictionary is set or not, on the first call of deflate - * we change to BUSY_STATE. - * (4) -- intentionally left blank -- :) - * (5) FINISHING_STATE is entered, when flush() is called to indicate that - * there is no more INPUT. There are also states indicating, that - * the header wasn't written yet. - * (6) FINISHED_STATE is entered, when everything has been flushed to the - * internal pending output buffer. - * (7) At any time (7) - * - */ - - #endregion Deflater Documentation - - #region Public Constants - - /// - /// The best and slowest compression level. This tries to find very - /// long and distant string repetitions. - /// - public const int BEST_COMPRESSION = 9; - - /// - /// The worst but fastest compression level. - /// - public const int BEST_SPEED = 1; - - /// - /// The default compression level. - /// - public const int DEFAULT_COMPRESSION = -1; - - /// - /// This level won't compress at all but output uncompressed blocks. - /// - public const int NO_COMPRESSION = 0; - - /// - /// The compression method. This is the only method supported so far. - /// There is no need to use this constant at all. - /// - public const int DEFLATED = 8; - - #endregion Public Constants - - #region Public Enum - - /// - /// Compression Level as an enum for safer use - /// - public enum CompressionLevel - { - /// - /// The best and slowest compression level. This tries to find very - /// long and distant string repetitions. - /// - BEST_COMPRESSION = Deflater.BEST_COMPRESSION, - - /// - /// The worst but fastest compression level. - /// - BEST_SPEED = Deflater.BEST_SPEED, - - /// - /// The default compression level. - /// - DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION, - - /// - /// This level won't compress at all but output uncompressed blocks. - /// - NO_COMPRESSION = Deflater.NO_COMPRESSION, - - /// - /// The compression method. This is the only method supported so far. - /// There is no need to use this constant at all. - /// - DEFLATED = Deflater.DEFLATED - } - - #endregion Public Enum - - #region Local Constants - - private const int IS_SETDICT = 0x01; - private const int IS_FLUSHING = 0x04; - private const int IS_FINISHING = 0x08; - - private const int INIT_STATE = 0x00; - private const int SETDICT_STATE = 0x01; - - // private static int INIT_FINISHING_STATE = 0x08; - // private static int SETDICT_FINISHING_STATE = 0x09; - private const int BUSY_STATE = 0x10; - - private const int FLUSHING_STATE = 0x14; - private const int FINISHING_STATE = 0x1c; - private const int FINISHED_STATE = 0x1e; - private const int CLOSED_STATE = 0x7f; - - #endregion Local Constants - - #region Constructors - - /// - /// Creates a new deflater with default compression level. - /// - public Deflater() : this(DEFAULT_COMPRESSION, false) - { - } - - /// - /// Creates a new deflater with given compression level. - /// - /// - /// the compression level, a value between NO_COMPRESSION - /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. - /// - /// if lvl is out of range. - public Deflater(int level) : this(level, false) - { - } - - /// - /// Creates a new deflater with given compression level. - /// - /// - /// the compression level, a value between NO_COMPRESSION - /// and BEST_COMPRESSION. - /// - /// - /// true, if we should suppress the Zlib/RFC1950 header at the - /// beginning and the adler checksum at the end of the output. This is - /// useful for the GZIP/PKZIP formats. - /// - /// if lvl is out of range. - public Deflater(int level, bool noZlibHeaderOrFooter) - { - if (level == DEFAULT_COMPRESSION) - { - level = 6; - } - else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) - { - throw new ArgumentOutOfRangeException(nameof(level)); - } - - pending = new DeflaterPending(); - engine = new DeflaterEngine(pending, noZlibHeaderOrFooter); - this.noZlibHeaderOrFooter = noZlibHeaderOrFooter; - SetStrategy(DeflateStrategy.Default); - SetLevel(level); - Reset(); - } - - #endregion Constructors - - /// - /// Resets the deflater. The deflater acts afterwards as if it was - /// just created with the same compression level and strategy as it - /// had before. - /// - public void Reset() - { - state = (noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE); - totalOut = 0; - pending.Reset(); - engine.Reset(); - } - - /// - /// Gets the current adler checksum of the data that was processed so far. - /// - public int Adler - { - get - { - return engine.Adler; - } - } - - /// - /// Gets the number of input bytes processed so far. - /// - public long TotalIn - { - get - { - return engine.TotalIn; - } - } - - /// - /// Gets the number of output bytes so far. - /// - public long TotalOut - { - get - { - return totalOut; - } - } - - /// - /// Flushes the current input block. Further calls to deflate() will - /// produce enough output to inflate everything in the current input - /// block. This is not part of Sun's JDK so I have made it package - /// private. It is used by DeflaterOutputStream to implement - /// flush(). - /// - public void Flush() - { - state |= IS_FLUSHING; - } - - /// - /// Finishes the deflater with the current input block. It is an error - /// to give more input after this method was called. This method must - /// be called to force all bytes to be flushed. - /// - public void Finish() - { - state |= (IS_FLUSHING | IS_FINISHING); - } - - /// - /// Returns true if the stream was finished and no more output bytes - /// are available. - /// - public bool IsFinished - { - get - { - return (state == FINISHED_STATE) && pending.IsFlushed; - } - } - - /// - /// Returns true, if the input buffer is empty. - /// You should then call setInput(). - /// NOTE: This method can also return true when the stream - /// was finished. - /// - public bool IsNeedingInput - { - get - { - return engine.NeedsInput(); - } - } - - /// - /// Sets the data which should be compressed next. This should be only - /// called when needsInput indicates that more input is needed. - /// If you call setInput when needsInput() returns false, the - /// previous input that is still pending will be thrown away. - /// The given byte array should not be changed, before needsInput() returns - /// true again. - /// This call is equivalent to setInput(input, 0, input.length). - /// - /// - /// the buffer containing the input data. - /// - /// - /// if the buffer was finished() or ended(). - /// - public void SetInput(byte[] input) - { - SetInput(input, 0, input.Length); - } - - /// - /// Sets the data which should be compressed next. This should be - /// only called when needsInput indicates that more input is needed. - /// The given byte array should not be changed, before needsInput() returns - /// true again. - /// - /// - /// the buffer containing the input data. - /// - /// - /// the start of the data. - /// - /// - /// the number of data bytes of input. - /// - /// - /// if the buffer was Finish()ed or if previous input is still pending. - /// - public void SetInput(byte[] input, int offset, int count) - { - if ((state & IS_FINISHING) != 0) - { - throw new InvalidOperationException("Finish() already called"); - } - engine.SetInput(input, offset, count); - } - - /// - /// Sets the compression level. There is no guarantee of the exact - /// position of the change, but if you call this when needsInput is - /// true the change of compression level will occur somewhere near - /// before the end of the so far given input. - /// - /// - /// the new compression level. - /// - public void SetLevel(int level) - { - if (level == DEFAULT_COMPRESSION) - { - level = 6; - } - else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) - { - throw new ArgumentOutOfRangeException(nameof(level)); - } - - if (this.level != level) - { - this.level = level; - engine.SetLevel(level); - } - } - - /// - /// Get current compression level - /// - /// Returns the current compression level - public int GetLevel() - { - return level; - } - - /// - /// Sets the compression strategy. Strategy is one of - /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact - /// position where the strategy is changed, the same as for - /// SetLevel() applies. - /// - /// - /// The new compression strategy. - /// - public void SetStrategy(DeflateStrategy strategy) - { - engine.Strategy = strategy; - } - - /// - /// Deflates the current input block with to the given array. - /// - /// - /// The buffer where compressed data is stored - /// - /// - /// The number of compressed bytes added to the output, or 0 if either - /// IsNeedingInput() or IsFinished returns true or length is zero. - /// - public int Deflate(byte[] output) - { - return Deflate(output, 0, output.Length); - } - - /// - /// Deflates the current input block to the given array. - /// - /// - /// Buffer to store the compressed data. - /// - /// - /// Offset into the output array. - /// - /// - /// The maximum number of bytes that may be stored. - /// - /// - /// The number of compressed bytes added to the output, or 0 if either - /// needsInput() or finished() returns true or length is zero. - /// - /// - /// If Finish() was previously called. - /// - /// - /// If offset or length don't match the array length. - /// - public int Deflate(byte[] output, int offset, int length) - { - int origLength = length; - - if (state == CLOSED_STATE) - { - throw new InvalidOperationException("Deflater closed"); - } - - if (state < BUSY_STATE) - { - // output header - int header = (DEFLATED + - ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; - int level_flags = (level - 1) >> 1; - if (level_flags < 0 || level_flags > 3) - { - level_flags = 3; - } - header |= level_flags << 6; - if ((state & IS_SETDICT) != 0) - { - // Dictionary was set - header |= DeflaterConstants.PRESET_DICT; - } - header += 31 - (header % 31); - - pending.WriteShortMSB(header); - if ((state & IS_SETDICT) != 0) - { - int chksum = engine.Adler; - engine.ResetAdler(); - pending.WriteShortMSB(chksum >> 16); - pending.WriteShortMSB(chksum & 0xffff); - } - - state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING)); - } - - for (; ; ) - { - int count = pending.Flush(output, offset, length); - offset += count; - totalOut += count; - length -= count; - - if (length == 0 || state == FINISHED_STATE) - { - break; - } - - if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0)) - { - switch (state) - { - case BUSY_STATE: - // We need more input now - return origLength - length; - - case FLUSHING_STATE: - if (level != NO_COMPRESSION) - { - /* We have to supply some lookahead. 8 bit lookahead - * is needed by the zlib inflater, and we must fill - * the next byte, so that all bits are flushed. - */ - int neededbits = 8 + ((-pending.BitCount) & 7); - while (neededbits > 0) - { - /* write a static tree block consisting solely of - * an EOF: - */ - pending.WriteBits(2, 10); - neededbits -= 10; - } - } - state = BUSY_STATE; - break; - - case FINISHING_STATE: - pending.AlignToByte(); - - // Compressed data is complete. Write footer information if required. - if (!noZlibHeaderOrFooter) - { - int adler = engine.Adler; - pending.WriteShortMSB(adler >> 16); - pending.WriteShortMSB(adler & 0xffff); - } - state = FINISHED_STATE; - break; - } - } - } - return origLength - length; - } - - /// - /// Sets the dictionary which should be used in the deflate process. - /// This call is equivalent to setDictionary(dict, 0, dict.Length). - /// - /// - /// the dictionary. - /// - /// - /// if SetInput () or Deflate () were already called or another dictionary was already set. - /// - public void SetDictionary(byte[] dictionary) - { - SetDictionary(dictionary, 0, dictionary.Length); - } - - /// - /// Sets the dictionary which should be used in the deflate process. - /// The dictionary is a byte array containing strings that are - /// likely to occur in the data which should be compressed. The - /// dictionary is not stored in the compressed output, only a - /// checksum. To decompress the output you need to supply the same - /// dictionary again. - /// - /// - /// The dictionary data - /// - /// - /// The index where dictionary information commences. - /// - /// - /// The number of bytes in the dictionary. - /// - /// - /// If SetInput () or Deflate() were already called or another dictionary was already set. - /// - public void SetDictionary(byte[] dictionary, int index, int count) - { - if (state != INIT_STATE) - { - throw new InvalidOperationException(); - } - - state = SETDICT_STATE; - engine.SetDictionary(dictionary, index, count); - } - - #region Instance Fields - - /// - /// Compression level. - /// - private int level; - - /// - /// If true no Zlib/RFC1950 headers or footers are generated - /// - private bool noZlibHeaderOrFooter; - - /// - /// The current state. - /// - private int state; - - /// - /// The total bytes of output written. - /// - private long totalOut; - - /// - /// The pending output. - /// - private DeflaterPending pending; - - /// - /// The deflater engine. - /// - private DeflaterEngine engine; - - #endregion Instance Fields - } + #region Deflater Documentation + + /* + * The Deflater can do the following state transitions: + * + * (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---. + * / | (2) (5) | + * / v (5) | + * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) + * \ | (3) | ,--------' + * | | | (3) / + * v v (5) v v + * (1) -> BUSY_STATE ----> FINISHING_STATE + * | (6) + * v + * FINISHED_STATE + * \_____________________________________/ + * | (7) + * v + * CLOSED_STATE + * + * (1) If we should produce a header we start in INIT_STATE, otherwise + * we start in BUSY_STATE. + * (2) A dictionary may be set only when we are in INIT_STATE, then + * we change the state as indicated. + * (3) Whether a dictionary is set or not, on the first call of deflate + * we change to BUSY_STATE. + * (4) -- intentionally left blank -- :) + * (5) FINISHING_STATE is entered, when flush() is called to indicate that + * there is no more INPUT. There are also states indicating, that + * the header wasn't written yet. + * (6) FINISHED_STATE is entered, when everything has been flushed to the + * internal pending output buffer. + * (7) At any time (7) + * + */ + + #endregion Deflater Documentation + + #region Public Constants + + /// + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + public const int BEST_COMPRESSION = 9; + + /// + /// The worst but fastest compression level. + /// + public const int BEST_SPEED = 1; + + /// + /// The default compression level. + /// + public const int DEFAULT_COMPRESSION = -1; + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + public const int NO_COMPRESSION = 0; + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + public const int DEFLATED = 8; + + #endregion Public Constants + + #region Public Enum + + /// + /// Compression Level as an enum for safer use + /// + public enum CompressionLevel + { + /// + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + BEST_COMPRESSION = Deflater.BEST_COMPRESSION, + + /// + /// The worst but fastest compression level. + /// + BEST_SPEED = Deflater.BEST_SPEED, + + /// + /// The default compression level. + /// + DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION, + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + NO_COMPRESSION = Deflater.NO_COMPRESSION, + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + DEFLATED = Deflater.DEFLATED + } + + #endregion Public Enum + + #region Local Constants + + private const int IS_SETDICT = 0x01; + private const int IS_FLUSHING = 0x04; + private const int IS_FINISHING = 0x08; + + private const int INIT_STATE = 0x00; + private const int SETDICT_STATE = 0x01; + + // private static int INIT_FINISHING_STATE = 0x08; + // private static int SETDICT_FINISHING_STATE = 0x09; + private const int BUSY_STATE = 0x10; + + private const int FLUSHING_STATE = 0x14; + private const int FINISHING_STATE = 0x1c; + private const int FINISHED_STATE = 0x1e; + private const int CLOSED_STATE = 0x7f; + + #endregion Local Constants + + #region Constructors + + /// + /// Creates a new deflater with default compression level. + /// + public Deflater() : this(DEFAULT_COMPRESSION, false) + { + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. + /// + /// if lvl is out of range. + public Deflater(int level) : this(level, false) + { + } + + /// + /// Creates a new deflater with given compression level. + /// + /// + /// the compression level, a value between NO_COMPRESSION + /// and BEST_COMPRESSION. + /// + /// + /// true, if we should suppress the Zlib/RFC1950 header at the + /// beginning and the adler checksum at the end of the output. This is + /// useful for the GZIP/PKZIP formats. + /// + /// if lvl is out of range. + public Deflater(int level, bool noZlibHeaderOrFooter) + { + if (level == DEFAULT_COMPRESSION) + { + level = 6; + } + else if (level is < NO_COMPRESSION or > BEST_COMPRESSION) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + pending = new DeflaterPending(); + engine = new DeflaterEngine(pending, noZlibHeaderOrFooter); + this.noZlibHeaderOrFooter = noZlibHeaderOrFooter; + SetStrategy(DeflateStrategy.Default); + SetLevel(level); + Reset(); + } + + #endregion Constructors + + /// + /// Resets the deflater. The deflater acts afterwards as if it was + /// just created with the same compression level and strategy as it + /// had before. + /// + public void Reset() + { + state = noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE; + totalOut = 0; + pending.Reset(); + engine.Reset(); + } + + /// + /// Gets the current adler checksum of the data that was processed so far. + /// + public int Adler + { + get + { + return engine.Adler; + } + } + + /// + /// Gets the number of input bytes processed so far. + /// + public long TotalIn + { + get + { + return engine.TotalIn; + } + } + + /// + /// Gets the number of output bytes so far. + /// + public long TotalOut + { + get + { + return totalOut; + } + } + + /// + /// Flushes the current input block. Further calls to deflate() will + /// produce enough output to inflate everything in the current input + /// block. This is not part of Sun's JDK so I have made it package + /// private. It is used by DeflaterOutputStream to implement + /// flush(). + /// + public void Flush() + { + state |= IS_FLUSHING; + } + + /// + /// Finishes the deflater with the current input block. It is an error + /// to give more input after this method was called. This method must + /// be called to force all bytes to be flushed. + /// + public void Finish() + { + state |= IS_FLUSHING | IS_FINISHING; + } + + /// + /// Returns true if the stream was finished and no more output bytes + /// are available. + /// + public bool IsFinished + { + get + { + return (state == FINISHED_STATE) && pending.IsFlushed; + } + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method can also return true when the stream + /// was finished. + /// + public bool IsNeedingInput + { + get + { + return engine.NeedsInput(); + } + } + + /// + /// Sets the data which should be compressed next. This should be only + /// called when needsInput indicates that more input is needed. + /// If you call setInput when needsInput() returns false, the + /// previous input that is still pending will be thrown away. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// This call is equivalent to setInput(input, 0, input.length). + /// + /// + /// the buffer containing the input data. + /// + /// + /// if the buffer was finished() or ended(). + /// + public void SetInput(byte[] input) + { + SetInput(input, 0, input.Length); + } + + /// + /// Sets the data which should be compressed next. This should be + /// only called when needsInput indicates that more input is needed. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// + /// + /// the buffer containing the input data. + /// + /// + /// the start of the data. + /// + /// + /// the number of data bytes of input. + /// + /// + /// if the buffer was Finish()ed or if previous input is still pending. + /// + public void SetInput(byte[] input, int offset, int count) + { + if ((state & IS_FINISHING) != 0) + { + throw new InvalidOperationException("Finish() already called"); + } + engine.SetInput(input, offset, count); + } + + /// + /// Sets the compression level. There is no guarantee of the exact + /// position of the change, but if you call this when needsInput is + /// true the change of compression level will occur somewhere near + /// before the end of the so far given input. + /// + /// + /// the new compression level. + /// + public void SetLevel(int level) + { + if (level == DEFAULT_COMPRESSION) + { + level = 6; + } + else if (level is < NO_COMPRESSION or > BEST_COMPRESSION) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + if (this.level != level) + { + this.level = level; + engine.SetLevel(level); + } + } + + /// + /// Get current compression level + /// + /// Returns the current compression level + public int GetLevel() + { + return level; + } + + /// + /// Sets the compression strategy. Strategy is one of + /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact + /// position where the strategy is changed, the same as for + /// SetLevel() applies. + /// + /// + /// The new compression strategy. + /// + public void SetStrategy(DeflateStrategy strategy) + { + engine.Strategy = strategy; + } + + /// + /// Deflates the current input block with to the given array. + /// + /// + /// The buffer where compressed data is stored + /// + /// + /// The number of compressed bytes added to the output, or 0 if either + /// IsNeedingInput() or IsFinished returns true or length is zero. + /// + public int Deflate(byte[] output) + { + return Deflate(output, 0, output.Length); + } + + /// + /// Deflates the current input block to the given array. + /// + /// + /// Buffer to store the compressed data. + /// + /// + /// Offset into the output array. + /// + /// + /// The maximum number of bytes that may be stored. + /// + /// + /// The number of compressed bytes added to the output, or 0 if either + /// needsInput() or finished() returns true or length is zero. + /// + /// + /// If Finish() was previously called. + /// + /// + /// If offset or length don't match the array length. + /// + public int Deflate(byte[] output, int offset, int length) + { + var origLength = length; + + if (state == CLOSED_STATE) + { + throw new InvalidOperationException("Deflater closed"); + } + + if (state < BUSY_STATE) + { + // output header + var header = (DEFLATED + + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; + var level_flags = (level - 1) >> 1; + if (level_flags is < 0 or > 3) + { + level_flags = 3; + } + header |= level_flags << 6; + if ((state & IS_SETDICT) != 0) + { + // Dictionary was set + header |= DeflaterConstants.PRESET_DICT; + } + header += 31 - (header % 31); + + pending.WriteShortMSB(header); + if ((state & IS_SETDICT) != 0) + { + var chksum = engine.Adler; + engine.ResetAdler(); + pending.WriteShortMSB(chksum >> 16); + pending.WriteShortMSB(chksum & 0xffff); + } + + state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING)); + } + + for (; ; ) + { + var count = pending.Flush(output, offset, length); + offset += count; + totalOut += count; + length -= count; + + if (length == 0 || state == FINISHED_STATE) + { + break; + } + + if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0)) + { + switch (state) + { + case BUSY_STATE: + // We need more input now + return origLength - length; + + case FLUSHING_STATE: + if (level != NO_COMPRESSION) + { + /* We have to supply some lookahead. 8 bit lookahead + * is needed by the zlib inflater, and we must fill + * the next byte, so that all bits are flushed. + */ + var neededbits = 8 + ((-pending.BitCount) & 7); + while (neededbits > 0) + { + /* write a static tree block consisting solely of + * an EOF: + */ + pending.WriteBits(2, 10); + neededbits -= 10; + } + } + state = BUSY_STATE; + break; + + case FINISHING_STATE: + pending.AlignToByte(); + + // Compressed data is complete. Write footer information if required. + if (!noZlibHeaderOrFooter) + { + var adler = engine.Adler; + pending.WriteShortMSB(adler >> 16); + pending.WriteShortMSB(adler & 0xffff); + } + state = FINISHED_STATE; + break; + } + } + } + return origLength - length; + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// This call is equivalent to setDictionary(dict, 0, dict.Length). + /// + /// + /// the dictionary. + /// + /// + /// if SetInput () or Deflate () were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dictionary) + { + SetDictionary(dictionary, 0, dictionary.Length); + } + + /// + /// Sets the dictionary which should be used in the deflate process. + /// The dictionary is a byte array containing strings that are + /// likely to occur in the data which should be compressed. The + /// dictionary is not stored in the compressed output, only a + /// checksum. To decompress the output you need to supply the same + /// dictionary again. + /// + /// + /// The dictionary data + /// + /// + /// The index where dictionary information commences. + /// + /// + /// The number of bytes in the dictionary. + /// + /// + /// If SetInput () or Deflate() were already called or another dictionary was already set. + /// + public void SetDictionary(byte[] dictionary, int index, int count) + { + if (state != INIT_STATE) + { + throw new InvalidOperationException(); + } + + state = SETDICT_STATE; + engine.SetDictionary(dictionary, index, count); + } + + #region Instance Fields + + /// + /// Compression level. + /// + private int level; + + /// + /// If true no Zlib/RFC1950 headers or footers are generated + /// + private readonly bool noZlibHeaderOrFooter; + + /// + /// The current state. + /// + private int state; + + /// + /// The total bytes of output written. + /// + private long totalOut; + + /// + /// The pending output. + /// + private readonly DeflaterPending pending; + + /// + /// The deflater engine. + /// + private readonly DeflaterEngine engine; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs index f1b526ec7..bb346ae91 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs @@ -1,146 +1,145 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// This class contains constants used for deflation. +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] +public static class DeflaterConstants { - /// - /// This class contains constants used for deflation. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] - public static class DeflaterConstants - { - /// - /// Set to true to enable debugging - /// - public const bool DEBUGGING = false; - - /// - /// Written to Zip file to identify a stored block - /// - public const int STORED_BLOCK = 0; - - /// - /// Identifies static tree in Zip file - /// - public const int STATIC_TREES = 1; - - /// - /// Identifies dynamic tree in Zip file - /// - public const int DYN_TREES = 2; - - /// - /// Header flag indicating a preset dictionary for deflation - /// - public const int PRESET_DICT = 0x20; - - /// - /// Sets internal buffer sizes for Huffman encoding - /// - public const int DEFAULT_MEM_LEVEL = 8; - - /// - /// Internal compression engine constant - /// - public const int MAX_MATCH = 258; - - /// - /// Internal compression engine constant - /// - public const int MIN_MATCH = 3; - - /// - /// Internal compression engine constant - /// - public const int MAX_WBITS = 15; - - /// - /// Internal compression engine constant - /// - public const int WSIZE = 1 << MAX_WBITS; - - /// - /// Internal compression engine constant - /// - public const int WMASK = WSIZE - 1; - - /// - /// Internal compression engine constant - /// - public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; - - /// - /// Internal compression engine constant - /// - public const int HASH_SIZE = 1 << HASH_BITS; - - /// - /// Internal compression engine constant - /// - public const int HASH_MASK = HASH_SIZE - 1; - - /// - /// Internal compression engine constant - /// - public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; - - /// - /// Internal compression engine constant - /// - public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; - - /// - /// Internal compression engine constant - /// - public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; - - /// - /// Internal compression engine constant - /// - public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); - - /// - /// Internal compression engine constant - /// - public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_STORED = 0; - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_FAST = 1; - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_SLOW = 2; - - /// - /// Internal compression engine constant - /// - public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; - - /// - /// Internal compression engine constant - /// - public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; - - /// - /// Internal compression engine constant - /// - public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; - - /// - /// Internal compression engine constant - /// - public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; - - /// - /// Internal compression engine constant - /// - public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; - } + /// + /// Set to true to enable debugging + /// + public const bool DEBUGGING = false; + + /// + /// Written to Zip file to identify a stored block + /// + public const int STORED_BLOCK = 0; + + /// + /// Identifies static tree in Zip file + /// + public const int STATIC_TREES = 1; + + /// + /// Identifies dynamic tree in Zip file + /// + public const int DYN_TREES = 2; + + /// + /// Header flag indicating a preset dictionary for deflation + /// + public const int PRESET_DICT = 0x20; + + /// + /// Sets internal buffer sizes for Huffman encoding + /// + public const int DEFAULT_MEM_LEVEL = 8; + + /// + /// Internal compression engine constant + /// + public const int MAX_MATCH = 258; + + /// + /// Internal compression engine constant + /// + public const int MIN_MATCH = 3; + + /// + /// Internal compression engine constant + /// + public const int MAX_WBITS = 15; + + /// + /// Internal compression engine constant + /// + public const int WSIZE = 1 << MAX_WBITS; + + /// + /// Internal compression engine constant + /// + public const int WMASK = WSIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; + + /// + /// Internal compression engine constant + /// + public const int HASH_SIZE = 1 << HASH_BITS; + + /// + /// Internal compression engine constant + /// + public const int HASH_MASK = HASH_SIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; + + /// + /// Internal compression engine constant + /// + public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; + + /// + /// Internal compression engine constant + /// + public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; + + /// + /// Internal compression engine constant + /// + public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); + + /// + /// Internal compression engine constant + /// + public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_STORED = 0; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_FAST = 1; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_SLOW = 2; + + /// + /// Internal compression engine constant + /// + public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; + + /// + /// Internal compression engine constant + /// + public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs index 44e64bd96..4dad1bf7d 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs @@ -1,113 +1,111 @@ using MelonLoader.ICSharpCode.SharpZipLib.Checksum; using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// Strategies for deflater +/// +public enum DeflateStrategy { - /// - /// Strategies for deflater - /// - public enum DeflateStrategy - { - /// - /// The default strategy - /// - Default = 0, - - /// - /// This strategy will only allow longer string repetitions. It is - /// useful for random data with a small character set. - /// - Filtered = 1, - - /// - /// This strategy will not look for string repetitions at all. It - /// only encodes with Huffman trees (which means, that more common - /// characters get a smaller encoding. - /// - HuffmanOnly = 2 - } - - // DEFLATE ALGORITHM: - // - // The uncompressed stream is inserted into the window array. When - // the window array is full the first half is thrown away and the - // second half is copied to the beginning. - // - // The head array is a hash table. Three characters build a hash value - // and they the value points to the corresponding index in window of - // the last string with this hash. The prev array implements a - // linked list of matches with the same hash: prev[index & WMASK] points - // to the previous index with the same hash. - // - - /// - /// Low level compression engine for deflate algorithm which uses a 32K sliding window - /// with secondary compression from Huffman/Shannon-Fano codes. - /// - public class DeflaterEngine - { - #region Constants - - private const int TooFar = 4096; - - #endregion Constants - - #region Constructors - - /// - /// Construct instance with pending buffer - /// Adler calculation will be performed - /// - /// - /// Pending buffer to use - /// - public DeflaterEngine(DeflaterPending pending) - : this (pending, false) - { - } - - - - /// - /// Construct instance with pending buffer - /// - /// - /// Pending buffer to use - /// - /// - /// If no adler calculation should be performed - /// - public DeflaterEngine(DeflaterPending pending, bool noAdlerCalculation) - { - this.pending = pending; - huffman = new DeflaterHuffman(pending); - if (!noAdlerCalculation) - adler = new Adler32(); - - window = new byte[2 * DeflaterConstants.WSIZE]; - head = new short[DeflaterConstants.HASH_SIZE]; - prev = new short[DeflaterConstants.WSIZE]; - - // We start at index 1, to avoid an implementation deficiency, that - // we cannot build a repeat pattern at index 0. - blockStart = strstart = 1; - } - - #endregion Constructors - - /// - /// Deflate drives actual compression of data - /// - /// True to flush input buffers - /// Finish deflation with the current input. - /// Returns true if progress has been made. - public bool Deflate(bool flush, bool finish) - { - bool progress; - do - { - FillWindow(); - bool canFlush = flush && (inputOff == inputEnd); + /// + /// The default strategy + /// + Default = 0, + + /// + /// This strategy will only allow longer string repetitions. It is + /// useful for random data with a small character set. + /// + Filtered = 1, + + /// + /// This strategy will not look for string repetitions at all. It + /// only encodes with Huffman trees (which means, that more common + /// characters get a smaller encoding. + /// + HuffmanOnly = 2 +} + +// DEFLATE ALGORITHM: +// +// The uncompressed stream is inserted into the window array. When +// the window array is full the first half is thrown away and the +// second half is copied to the beginning. +// +// The head array is a hash table. Three characters build a hash value +// and they the value points to the corresponding index in window of +// the last string with this hash. The prev array implements a +// linked list of matches with the same hash: prev[index & WMASK] points +// to the previous index with the same hash. +// + +/// +/// Low level compression engine for deflate algorithm which uses a 32K sliding window +/// with secondary compression from Huffman/Shannon-Fano codes. +/// +public class DeflaterEngine +{ + #region Constants + + private const int TooFar = 4096; + + #endregion Constants + + #region Constructors + + /// + /// Construct instance with pending buffer + /// Adler calculation will be performed + /// + /// + /// Pending buffer to use + /// + public DeflaterEngine(DeflaterPending pending) + : this(pending, false) + { + } + + /// + /// Construct instance with pending buffer + /// + /// + /// Pending buffer to use + /// + /// + /// If no adler calculation should be performed + /// + public DeflaterEngine(DeflaterPending pending, bool noAdlerCalculation) + { + this.pending = pending; + huffman = new DeflaterHuffman(pending); + if (!noAdlerCalculation) + adler = new Adler32(); + + window = new byte[2 * DeflaterConstants.WSIZE]; + head = new short[DeflaterConstants.HASH_SIZE]; + prev = new short[DeflaterConstants.WSIZE]; + + // We start at index 1, to avoid an implementation deficiency, that + // we cannot build a repeat pattern at index 0. + blockStart = strstart = 1; + } + + #endregion Constructors + + /// + /// Deflate drives actual compression of data + /// + /// True to flush input buffers + /// Finish deflation with the current input. + /// Returns true if progress has been made. + public bool Deflate(bool flush, bool finish) + { + bool progress; + do + { + FillWindow(); + var canFlush = flush && (inputOff == inputEnd); #if DebugDeflation if (DeflaterConstants.DEBUGGING) { @@ -115,309 +113,309 @@ public bool Deflate(bool flush, bool finish) + lookahead + "], " + compressionFunction + "," + canFlush); } #endif - switch (compressionFunction) - { - case DeflaterConstants.DEFLATE_STORED: - progress = DeflateStored(canFlush, finish); - break; - - case DeflaterConstants.DEFLATE_FAST: - progress = DeflateFast(canFlush, finish); - break; - - case DeflaterConstants.DEFLATE_SLOW: - progress = DeflateSlow(canFlush, finish); - break; - - default: - throw new InvalidOperationException("unknown compressionFunction"); - } - } while (pending.IsFlushed && progress); // repeat while we have no pending output and progress was made - return progress; - } - - /// - /// Sets input data to be deflated. Should only be called when NeedsInput() - /// returns true - /// - /// The buffer containing input data. - /// The offset of the first byte of data. - /// The number of bytes of data to use as input. - public void SetInput(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - if (inputOff < inputEnd) - { - throw new InvalidOperationException("Old input was not completely processed"); - } - - int end = offset + count; - - /* We want to throw an ArrayIndexOutOfBoundsException early. The - * check is very tricky: it also handles integer wrap around. - */ - if ((offset > end) || (end > buffer.Length)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - inputBuf = buffer; - inputOff = offset; - inputEnd = end; - } - - /// - /// Determines if more input is needed. - /// - /// Return true if input is needed via SetInput - public bool NeedsInput() - { - return (inputEnd == inputOff); - } - - /// - /// Set compression dictionary - /// - /// The buffer containing the dictionary data - /// The offset in the buffer for the first byte of data - /// The length of the dictionary data. - public void SetDictionary(byte[] buffer, int offset, int length) - { + switch (compressionFunction) + { + case DeflaterConstants.DEFLATE_STORED: + progress = DeflateStored(canFlush, finish); + break; + + case DeflaterConstants.DEFLATE_FAST: + progress = DeflateFast(canFlush, finish); + break; + + case DeflaterConstants.DEFLATE_SLOW: + progress = DeflateSlow(canFlush, finish); + break; + + default: + throw new InvalidOperationException("unknown compressionFunction"); + } + } while (pending.IsFlushed && progress); // repeat while we have no pending output and progress was made + return progress; + } + + /// + /// Sets input data to be deflated. Should only be called when NeedsInput() + /// returns true + /// + /// The buffer containing input data. + /// The offset of the first byte of data. + /// The number of bytes of data to use as input. + public void SetInput(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (inputOff < inputEnd) + { + throw new InvalidOperationException("Old input was not completely processed"); + } + + var end = offset + count; + + /* We want to throw an ArrayIndexOutOfBoundsException early. The + * check is very tricky: it also handles integer wrap around. + */ + if ((offset > end) || (end > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + inputBuf = buffer; + inputOff = offset; + inputEnd = end; + } + + /// + /// Determines if more input is needed. + /// + /// Return true if input is needed via SetInput + public bool NeedsInput() + { + return inputEnd == inputOff; + } + + /// + /// Set compression dictionary + /// + /// The buffer containing the dictionary data + /// The offset in the buffer for the first byte of data + /// The length of the dictionary data. + public void SetDictionary(byte[] buffer, int offset, int length) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (strstart != 1) ) { throw new InvalidOperationException("strstart not 1"); } #endif - adler?.Update(new ArraySegment(buffer, offset, length)); - if (length < DeflaterConstants.MIN_MATCH) - { - return; - } - - if (length > DeflaterConstants.MAX_DIST) - { - offset += length - DeflaterConstants.MAX_DIST; - length = DeflaterConstants.MAX_DIST; - } - - System.Array.Copy(buffer, offset, window, strstart, length); - - UpdateHash(); - --length; - while (--length > 0) - { - InsertString(); - strstart++; - } - strstart += 2; - blockStart = strstart; - } - - /// - /// Reset internal state - /// - public void Reset() - { - huffman.Reset(); - adler?.Reset(); - blockStart = strstart = 1; - lookahead = 0; - totalIn = 0; - prevAvailable = false; - matchLen = DeflaterConstants.MIN_MATCH - 1; - - for (int i = 0; i < DeflaterConstants.HASH_SIZE; i++) - { - head[i] = 0; - } - - for (int i = 0; i < DeflaterConstants.WSIZE; i++) - { - prev[i] = 0; - } - } - - /// - /// Reset Adler checksum - /// - public void ResetAdler() - { - adler?.Reset(); - } - - /// - /// Get current value of Adler checksum - /// - public int Adler - { - get - { - return (adler != null) ? unchecked((int)adler.Value) : 0; - } - } - - /// - /// Total data processed - /// - public long TotalIn - { - get - { - return totalIn; - } - } - - /// - /// Get/set the deflate strategy - /// - public DeflateStrategy Strategy - { - get - { - return strategy; - } - set - { - strategy = value; - } - } - - /// - /// Set the deflate level (0-9) - /// - /// The value to set the level to. - public void SetLevel(int level) - { - if ((level < 0) || (level > 9)) - { - throw new ArgumentOutOfRangeException(nameof(level)); - } - - goodLength = DeflaterConstants.GOOD_LENGTH[level]; - max_lazy = DeflaterConstants.MAX_LAZY[level]; - niceLength = DeflaterConstants.NICE_LENGTH[level]; - max_chain = DeflaterConstants.MAX_CHAIN[level]; - - if (DeflaterConstants.COMPR_FUNC[level] != compressionFunction) - { + adler?.Update(new ArraySegment(buffer, offset, length)); + if (length < DeflaterConstants.MIN_MATCH) + { + return; + } + + if (length > DeflaterConstants.MAX_DIST) + { + offset += length - DeflaterConstants.MAX_DIST; + length = DeflaterConstants.MAX_DIST; + } + + System.Array.Copy(buffer, offset, window, strstart, length); + + UpdateHash(); + --length; + while (--length > 0) + { + InsertString(); + strstart++; + } + strstart += 2; + blockStart = strstart; + } + + /// + /// Reset internal state + /// + public void Reset() + { + huffman.Reset(); + adler?.Reset(); + blockStart = strstart = 1; + lookahead = 0; + totalIn = 0; + prevAvailable = false; + matchLen = DeflaterConstants.MIN_MATCH - 1; + + for (var i = 0; i < DeflaterConstants.HASH_SIZE; i++) + { + head[i] = 0; + } + + for (var i = 0; i < DeflaterConstants.WSIZE; i++) + { + prev[i] = 0; + } + } + + /// + /// Reset Adler checksum + /// + public void ResetAdler() + { + adler?.Reset(); + } + + /// + /// Get current value of Adler checksum + /// + public int Adler + { + get + { + return (adler != null) ? unchecked((int)adler.Value) : 0; + } + } + + /// + /// Total data processed + /// + public long TotalIn + { + get + { + return totalIn; + } + } + + /// + /// Get/set the deflate strategy + /// + public DeflateStrategy Strategy + { + get + { + return strategy; + } + set + { + strategy = value; + } + } + + /// + /// Set the deflate level (0-9) + /// + /// The value to set the level to. + public void SetLevel(int level) + { + if (level is < 0 or > 9) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + goodLength = DeflaterConstants.GOOD_LENGTH[level]; + max_lazy = DeflaterConstants.MAX_LAZY[level]; + niceLength = DeflaterConstants.NICE_LENGTH[level]; + max_chain = DeflaterConstants.MAX_CHAIN[level]; + + if (DeflaterConstants.COMPR_FUNC[level] != compressionFunction) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING) { Console.WriteLine("Change from " + compressionFunction + " to " + DeflaterConstants.COMPR_FUNC[level]); } #endif - switch (compressionFunction) - { - case DeflaterConstants.DEFLATE_STORED: - if (strstart > blockStart) - { - huffman.FlushStoredBlock(window, blockStart, - strstart - blockStart, false); - blockStart = strstart; - } - UpdateHash(); - break; - - case DeflaterConstants.DEFLATE_FAST: - if (strstart > blockStart) - { - huffman.FlushBlock(window, blockStart, strstart - blockStart, - false); - blockStart = strstart; - } - break; - - case DeflaterConstants.DEFLATE_SLOW: - if (prevAvailable) - { - huffman.TallyLit(window[strstart - 1] & 0xff); - } - if (strstart > blockStart) - { - huffman.FlushBlock(window, blockStart, strstart - blockStart, false); - blockStart = strstart; - } - prevAvailable = false; - matchLen = DeflaterConstants.MIN_MATCH - 1; - break; - } - compressionFunction = DeflaterConstants.COMPR_FUNC[level]; - } - } - - /// - /// Fill the window - /// - public void FillWindow() - { - /* If the window is almost full and there is insufficient lookahead, - * move the upper half to the lower one to make room in the upper half. - */ - if (strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) - { - SlideWindow(); - } - - /* If there is not enough lookahead, but still some input left, - * read in the input - */ - if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) - { - int more = 2 * DeflaterConstants.WSIZE - lookahead - strstart; - - if (more > inputEnd - inputOff) - { - more = inputEnd - inputOff; - } - - System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more); - adler?.Update(new ArraySegment(inputBuf, inputOff, more)); - - inputOff += more; - totalIn += more; - lookahead += more; - } - - if (lookahead >= DeflaterConstants.MIN_MATCH) - { - UpdateHash(); - } - } - - private void UpdateHash() - { - /* - if (DEBUGGING) { - Console.WriteLine("updateHash: "+strstart); - } - */ - ins_h = (window[strstart] << DeflaterConstants.HASH_SHIFT) ^ window[strstart + 1]; - } - - /// - /// Inserts the current string in the head hash and returns the previous - /// value for this hash. - /// - /// The previous hash value - private int InsertString() - { - short match; - int hash = ((ins_h << DeflaterConstants.HASH_SHIFT) ^ window[strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; + switch (compressionFunction) + { + case DeflaterConstants.DEFLATE_STORED: + if (strstart > blockStart) + { + huffman.FlushStoredBlock(window, blockStart, + strstart - blockStart, false); + blockStart = strstart; + } + UpdateHash(); + break; + + case DeflaterConstants.DEFLATE_FAST: + if (strstart > blockStart) + { + huffman.FlushBlock(window, blockStart, strstart - blockStart, + false); + blockStart = strstart; + } + break; + + case DeflaterConstants.DEFLATE_SLOW: + if (prevAvailable) + { + huffman.TallyLit(window[strstart - 1] & 0xff); + } + if (strstart > blockStart) + { + huffman.FlushBlock(window, blockStart, strstart - blockStart, false); + blockStart = strstart; + } + prevAvailable = false; + matchLen = DeflaterConstants.MIN_MATCH - 1; + break; + } + compressionFunction = DeflaterConstants.COMPR_FUNC[level]; + } + } + + /// + /// Fill the window + /// + public void FillWindow() + { + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) + { + SlideWindow(); + } + + /* If there is not enough lookahead, but still some input left, + * read in the input + */ + if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) + { + var more = (2 * DeflaterConstants.WSIZE) - lookahead - strstart; + + if (more > inputEnd - inputOff) + { + more = inputEnd - inputOff; + } + + System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more); + adler?.Update(new ArraySegment(inputBuf, inputOff, more)); + + inputOff += more; + totalIn += more; + lookahead += more; + } + + if (lookahead >= DeflaterConstants.MIN_MATCH) + { + UpdateHash(); + } + } + + private void UpdateHash() + { + /* + if (DEBUGGING) { + Console.WriteLine("updateHash: "+strstart); + } + */ + ins_h = (window[strstart] << DeflaterConstants.HASH_SHIFT) ^ window[strstart + 1]; + } + + /// + /// Inserts the current string in the head hash and returns the previous + /// value for this hash. + /// + /// The previous hash value + private int InsertString() + { + short match; + var hash = ((ins_h << DeflaterConstants.HASH_SHIFT) ^ window[strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; #if DebugDeflation if (DeflaterConstants.DEBUGGING) @@ -432,207 +430,216 @@ private int InsertString() } } #endif - prev[strstart & DeflaterConstants.WMASK] = match = head[hash]; - head[hash] = unchecked((short)strstart); - ins_h = hash; - return match & 0xffff; - } - - private void SlideWindow() - { - Array.Copy(window, DeflaterConstants.WSIZE, window, 0, DeflaterConstants.WSIZE); - matchStart -= DeflaterConstants.WSIZE; - strstart -= DeflaterConstants.WSIZE; - blockStart -= DeflaterConstants.WSIZE; - - // Slide the hash table (could be avoided with 32 bit values - // at the expense of memory usage). - for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) - { - int m = head[i] & 0xffff; - head[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); - } - - // Slide the prev table. - for (int i = 0; i < DeflaterConstants.WSIZE; i++) - { - int m = prev[i] & 0xffff; - prev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); - } - } - - /// - /// Find the best (longest) string in the window matching the - /// string starting at strstart. - /// - /// Preconditions: - /// - /// strstart + DeflaterConstants.MAX_MATCH <= window.length. - /// - /// - /// True if a match greater than the minimum length is found - private bool FindLongestMatch(int curMatch) - { - int match; - int scan = strstart; - // scanMax is the highest position that we can look at - int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, lookahead) - 1; - int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0); - - byte[] window = this.window; - short[] prev = this.prev; - int chainLength = this.max_chain; - int niceLength = Math.Min(this.niceLength, lookahead); - - matchLen = Math.Max(matchLen, DeflaterConstants.MIN_MATCH - 1); - - if (scan + matchLen > scanMax) return false; - - byte scan_end1 = window[scan + matchLen - 1]; - byte scan_end = window[scan + matchLen]; - - // Do not waste too much time if we already have a good match: - if (matchLen >= this.goodLength) chainLength >>= 2; - - do - { - match = curMatch; - scan = strstart; - - if (window[match + matchLen] != scan_end - || window[match + matchLen - 1] != scan_end1 - || window[match] != window[scan] - || window[++match] != window[++scan]) - { - continue; - } - - // scan is set to strstart+1 and the comparison passed, so - // scanMax - scan is the maximum number of bytes we can compare. - // below we compare 8 bytes at a time, so first we compare - // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8 - - switch ((scanMax - scan) % 8) - { - case 1: - if (window[++scan] == window[++match]) break; - break; - - case 2: - if (window[++scan] == window[++match] - && window[++scan] == window[++match]) break; - break; - - case 3: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) break; - break; - - case 4: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) break; - break; - - case 5: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) break; - break; - - case 6: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) break; - break; - - case 7: - if (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]) break; - break; - } - - if (window[scan] == window[match]) - { - /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart + 258 unless lookahead is - * exhausted first. - */ - do - { - if (scan == scanMax) - { - ++scan; // advance to first position not matched - ++match; - - break; - } - } - while (window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match] - && window[++scan] == window[++match]); - } - - if (scan - strstart > matchLen) - { + prev[strstart & DeflaterConstants.WMASK] = match = head[hash]; + head[hash] = unchecked((short)strstart); + ins_h = hash; + return match & 0xffff; + } + + private void SlideWindow() + { + Array.Copy(window, DeflaterConstants.WSIZE, window, 0, DeflaterConstants.WSIZE); + matchStart -= DeflaterConstants.WSIZE; + strstart -= DeflaterConstants.WSIZE; + blockStart -= DeflaterConstants.WSIZE; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). + for (var i = 0; i < DeflaterConstants.HASH_SIZE; ++i) + { + var m = head[i] & 0xffff; + head[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); + } + + // Slide the prev table. + for (var i = 0; i < DeflaterConstants.WSIZE; i++) + { + var m = prev[i] & 0xffff; + prev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); + } + } + + /// + /// Find the best (longest) string in the window matching the + /// string starting at strstart. + /// + /// Preconditions: + /// + /// strstart + DeflaterConstants.MAX_MATCH <= window.length. + /// + /// + /// True if a match greater than the minimum length is found + private bool FindLongestMatch(int curMatch) + { + int match; + var scan = strstart; + // scanMax is the highest position that we can look at + var scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, lookahead) - 1; + var limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0); + + var window = this.window; + var prev = this.prev; + var chainLength = this.max_chain; + var niceLength = Math.Min(this.niceLength, lookahead); + + matchLen = Math.Max(matchLen, DeflaterConstants.MIN_MATCH - 1); + + if (scan + matchLen > scanMax) + return false; + + var scan_end1 = window[scan + matchLen - 1]; + var scan_end = window[scan + matchLen]; + + // Do not waste too much time if we already have a good match: + if (matchLen >= this.goodLength) + chainLength >>= 2; + + do + { + match = curMatch; + scan = strstart; + + if (window[match + matchLen] != scan_end + || window[match + matchLen - 1] != scan_end1 + || window[match] != window[scan] + || window[++match] != window[++scan]) + { + continue; + } + + // scan is set to strstart+1 and the comparison passed, so + // scanMax - scan is the maximum number of bytes we can compare. + // below we compare 8 bytes at a time, so first we compare + // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8 + + switch ((scanMax - scan) % 8) + { + case 1: + if (window[++scan] == window[++match]) + break; + break; + + case 2: + if (window[++scan] == window[++match] + && window[++scan] == window[++match]) + break; + break; + + case 3: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) + break; + break; + + case 4: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) + break; + break; + + case 5: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) + break; + break; + + case 6: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) + break; + break; + + case 7: + if (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]) + break; + break; + } + + if (window[scan] == window[match]) + { + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart + 258 unless lookahead is + * exhausted first. + */ + do + { + if (scan == scanMax) + { + ++scan; // advance to first position not matched + ++match; + + break; + } + } + while (window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match] + && window[++scan] == window[++match]); + } + + if (scan - strstart > matchLen) + { #if DebugDeflation - if (DeflaterConstants.DEBUGGING && (ins_h == 0) ) - Console.Error.WriteLine("Found match: " + curMatch + "-" + (scan - strstart)); + if (DeflaterConstants.DEBUGGING && (ins_h == 0) ) + Console.Error.WriteLine("Found match: " + curMatch + "-" + (scan - strstart)); #endif - matchStart = curMatch; - matchLen = scan - strstart; + matchStart = curMatch; + matchLen = scan - strstart; - if (matchLen >= niceLength) - break; + if (matchLen >= niceLength) + break; - scan_end1 = window[scan - 1]; - scan_end = window[scan]; - } - } while ((curMatch = (prev[curMatch & DeflaterConstants.WMASK] & 0xffff)) > limit && 0 != --chainLength); + scan_end1 = window[scan - 1]; + scan_end = window[scan]; + } + } while ((curMatch = prev[curMatch & DeflaterConstants.WMASK] & 0xffff) > limit && 0 != --chainLength); - return matchLen >= DeflaterConstants.MIN_MATCH; - } + return matchLen >= DeflaterConstants.MIN_MATCH; + } - private bool DeflateStored(bool flush, bool finish) - { - if (!flush && (lookahead == 0)) - { - return false; - } + private bool DeflateStored(bool flush, bool finish) + { + if (!flush && (lookahead == 0)) + { + return false; + } - strstart += lookahead; - lookahead = 0; + strstart += lookahead; + lookahead = 0; - int storedLength = strstart - blockStart; + var storedLength = strstart - blockStart; - if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full - (blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window - flush) - { - bool lastBlock = finish; - if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) - { - storedLength = DeflaterConstants.MAX_BLOCK_SIZE; - lastBlock = false; - } + if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full + (blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window + flush) + { + var lastBlock = finish; + if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) + { + storedLength = DeflaterConstants.MAX_BLOCK_SIZE; + lastBlock = false; + } #if DebugDeflation if (DeflaterConstants.DEBUGGING) @@ -641,47 +648,47 @@ private bool DeflateStored(bool flush, bool finish) } #endif - huffman.FlushStoredBlock(window, blockStart, storedLength, lastBlock); - blockStart += storedLength; - return !(lastBlock || storedLength == 0); - } - return true; - } - - private bool DeflateFast(bool flush, bool finish) - { - if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) - { - return false; - } - - while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) - { - if (lookahead == 0) - { - // We are flushing everything - huffman.FlushBlock(window, blockStart, strstart - blockStart, finish); - blockStart = strstart; - return false; - } - - if (strstart > 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD) - { - /* slide window, as FindLongestMatch needs this. - * This should only happen when flushing and the window - * is almost full. - */ - SlideWindow(); - } - - int hashHead; - if (lookahead >= DeflaterConstants.MIN_MATCH && - (hashHead = InsertString()) != 0 && - strategy != DeflateStrategy.HuffmanOnly && - strstart - hashHead <= DeflaterConstants.MAX_DIST && - FindLongestMatch(hashHead)) - { - // longestMatch sets matchStart and matchLen + huffman.FlushStoredBlock(window, blockStart, storedLength, lastBlock); + blockStart += storedLength; + return !(lastBlock || storedLength == 0); + } + return true; + } + + private bool DeflateFast(bool flush, bool finish) + { + if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) + { + return false; + } + + while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (lookahead == 0) + { + // We are flushing everything + huffman.FlushBlock(window, blockStart, strstart - blockStart, finish); + blockStart = strstart; + return false; + } + + if (strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + { + /* slide window, as FindLongestMatch needs this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + int hashHead; + if (lookahead >= DeflaterConstants.MIN_MATCH && + (hashHead = InsertString()) != 0 && + strategy != DeflateStrategy.HuffmanOnly && + strstart - hashHead <= DeflaterConstants.MAX_DIST && + FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen #if DebugDeflation if (DeflaterConstants.DEBUGGING) { @@ -693,114 +700,114 @@ private bool DeflateFast(bool flush, bool finish) } #endif - bool full = huffman.TallyDist(strstart - matchStart, matchLen); - - lookahead -= matchLen; - if (matchLen <= max_lazy && lookahead >= DeflaterConstants.MIN_MATCH) - { - while (--matchLen > 0) - { - ++strstart; - InsertString(); - } - ++strstart; - } - else - { - strstart += matchLen; - if (lookahead >= DeflaterConstants.MIN_MATCH - 1) - { - UpdateHash(); - } - } - matchLen = DeflaterConstants.MIN_MATCH - 1; - if (!full) - { - continue; - } - } - else - { - // No match found - huffman.TallyLit(window[strstart] & 0xff); - ++strstart; - --lookahead; - } - - if (huffman.IsFull()) - { - bool lastBlock = finish && (lookahead == 0); - huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock); - blockStart = strstart; - return !lastBlock; - } - } - return true; - } - - private bool DeflateSlow(bool flush, bool finish) - { - if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) - { - return false; - } - - while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) - { - if (lookahead == 0) - { - if (prevAvailable) - { - huffman.TallyLit(window[strstart - 1] & 0xff); - } - prevAvailable = false; - - // We are flushing everything + var full = huffman.TallyDist(strstart - matchStart, matchLen); + + lookahead -= matchLen; + if (matchLen <= max_lazy && lookahead >= DeflaterConstants.MIN_MATCH) + { + while (--matchLen > 0) + { + ++strstart; + InsertString(); + } + ++strstart; + } + else + { + strstart += matchLen; + if (lookahead >= DeflaterConstants.MIN_MATCH - 1) + { + UpdateHash(); + } + } + matchLen = DeflaterConstants.MIN_MATCH - 1; + if (!full) + { + continue; + } + } + else + { + // No match found + huffman.TallyLit(window[strstart] & 0xff); + ++strstart; + --lookahead; + } + + if (huffman.IsFull()) + { + var lastBlock = finish && (lookahead == 0); + huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock); + blockStart = strstart; + return !lastBlock; + } + } + return true; + } + + private bool DeflateSlow(bool flush, bool finish) + { + if (lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) + { + return false; + } + + while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (lookahead == 0) + { + if (prevAvailable) + { + huffman.TallyLit(window[strstart - 1] & 0xff); + } + prevAvailable = false; + + // We are flushing everything #if DebugDeflation if (DeflaterConstants.DEBUGGING && !flush) { throw new SharpZipBaseException("Not flushing, but no lookahead"); } #endif - huffman.FlushBlock(window, blockStart, strstart - blockStart, - finish); - blockStart = strstart; - return false; - } - - if (strstart >= 2 * DeflaterConstants.WSIZE - DeflaterConstants.MIN_LOOKAHEAD) - { - /* slide window, as FindLongestMatch needs this. - * This should only happen when flushing and the window - * is almost full. - */ - SlideWindow(); - } - - int prevMatch = matchStart; - int prevLen = matchLen; - if (lookahead >= DeflaterConstants.MIN_MATCH) - { - int hashHead = InsertString(); - - if (strategy != DeflateStrategy.HuffmanOnly && - hashHead != 0 && - strstart - hashHead <= DeflaterConstants.MAX_DIST && - FindLongestMatch(hashHead)) - { - // longestMatch sets matchStart and matchLen - - // Discard match if too small and too far away - if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == DeflaterConstants.MIN_MATCH && strstart - matchStart > TooFar))) - { - matchLen = DeflaterConstants.MIN_MATCH - 1; - } - } - } - - // previous match was better - if ((prevLen >= DeflaterConstants.MIN_MATCH) && (matchLen <= prevLen)) - { + huffman.FlushBlock(window, blockStart, strstart - blockStart, + finish); + blockStart = strstart; + return false; + } + + if (strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD) + { + /* slide window, as FindLongestMatch needs this. + * This should only happen when flushing and the window + * is almost full. + */ + SlideWindow(); + } + + var prevMatch = matchStart; + var prevLen = matchLen; + if (lookahead >= DeflaterConstants.MIN_MATCH) + { + var hashHead = InsertString(); + + if (strategy != DeflateStrategy.HuffmanOnly && + hashHead != 0 && + strstart - hashHead <= DeflaterConstants.MAX_DIST && + FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen + + // Discard match if too small and too far away + if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == DeflaterConstants.MIN_MATCH && strstart - matchStart > TooFar))) + { + matchLen = DeflaterConstants.MIN_MATCH - 1; + } + } + } + + // previous match was better + if ((prevLen >= DeflaterConstants.MIN_MATCH) && (matchLen <= prevLen)) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING) { @@ -810,137 +817,136 @@ private bool DeflateSlow(bool flush, bool finish) } } #endif - huffman.TallyDist(strstart - 1 - prevMatch, prevLen); - prevLen -= 2; - do - { - strstart++; - lookahead--; - if (lookahead >= DeflaterConstants.MIN_MATCH) - { - InsertString(); - } - } while (--prevLen > 0); - - strstart++; - lookahead--; - prevAvailable = false; - matchLen = DeflaterConstants.MIN_MATCH - 1; - } - else - { - if (prevAvailable) - { - huffman.TallyLit(window[strstart - 1] & 0xff); - } - prevAvailable = true; - strstart++; - lookahead--; - } - - if (huffman.IsFull()) - { - int len = strstart - blockStart; - if (prevAvailable) - { - len--; - } - bool lastBlock = (finish && (lookahead == 0) && !prevAvailable); - huffman.FlushBlock(window, blockStart, len, lastBlock); - blockStart += len; - return !lastBlock; - } - } - return true; - } - - #region Instance Fields - - // Hash index of string to be inserted - private int ins_h; - - /// - /// Hashtable, hashing three characters to an index for window, so - /// that window[index]..window[index+2] have this hash code. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xffff. - /// - private short[] head; - - /// - /// prev[index & WMASK] points to the previous index that has the - /// same hash code as the string starting at index. This way - /// entries with the same hash code are in a linked list. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xffff. - /// - private short[] prev; - - private int matchStart; - - // Length of best match - private int matchLen; - - // Set if previous match exists - private bool prevAvailable; - - private int blockStart; - - /// - /// Points to the current character in the window. - /// - private int strstart; - - /// - /// lookahead is the number of characters starting at strstart in - /// window that are valid. - /// So window[strstart] until window[strstart+lookahead-1] are valid - /// characters. - /// - private int lookahead; - - /// - /// This array contains the part of the uncompressed stream that - /// is of relevance. The current character is indexed by strstart. - /// - private byte[] window; - - private DeflateStrategy strategy; - private int max_chain, max_lazy, niceLength, goodLength; - - /// - /// The current compression function. - /// - private int compressionFunction; - - /// - /// The input data for compression. - /// - private byte[] inputBuf; - - /// - /// The total bytes of input read. - /// - private long totalIn; - - /// - /// The offset into inputBuf, where input data starts. - /// - private int inputOff; - - /// - /// The end offset of the input data. - /// - private int inputEnd; - - private DeflaterPending pending; - private DeflaterHuffman huffman; - - /// - /// The adler checksum - /// - private Adler32 adler; - - #endregion Instance Fields - } + huffman.TallyDist(strstart - 1 - prevMatch, prevLen); + prevLen -= 2; + do + { + strstart++; + lookahead--; + if (lookahead >= DeflaterConstants.MIN_MATCH) + { + InsertString(); + } + } while (--prevLen > 0); + + strstart++; + lookahead--; + prevAvailable = false; + matchLen = DeflaterConstants.MIN_MATCH - 1; + } + else + { + if (prevAvailable) + { + huffman.TallyLit(window[strstart - 1] & 0xff); + } + prevAvailable = true; + strstart++; + lookahead--; + } + + if (huffman.IsFull()) + { + var len = strstart - blockStart; + if (prevAvailable) + { + len--; + } + var lastBlock = finish && (lookahead == 0) && !prevAvailable; + huffman.FlushBlock(window, blockStart, len, lastBlock); + blockStart += len; + return !lastBlock; + } + } + return true; + } + + #region Instance Fields + + // Hash index of string to be inserted + private int ins_h; + + /// + /// Hashtable, hashing three characters to an index for window, so + /// that window[index]..window[index+2] have this hash code. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xffff. + /// + private readonly short[] head; + + /// + /// prev[index & WMASK] points to the previous index that has the + /// same hash code as the string starting at index. This way + /// entries with the same hash code are in a linked list. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xffff. + /// + private readonly short[] prev; + + private int matchStart; + + // Length of best match + private int matchLen; + + // Set if previous match exists + private bool prevAvailable; + + private int blockStart; + + /// + /// Points to the current character in the window. + /// + private int strstart; + + /// + /// lookahead is the number of characters starting at strstart in + /// window that are valid. + /// So window[strstart] until window[strstart+lookahead-1] are valid + /// characters. + /// + private int lookahead; + + /// + /// This array contains the part of the uncompressed stream that + /// is of relevance. The current character is indexed by strstart. + /// + private readonly byte[] window; + + private DeflateStrategy strategy; + private int max_chain, max_lazy, niceLength, goodLength; + + /// + /// The current compression function. + /// + private int compressionFunction; + + /// + /// The input data for compression. + /// + private byte[] inputBuf; + + /// + /// The total bytes of input read. + /// + private long totalIn; + + /// + /// The offset into inputBuf, where input data starts. + /// + private int inputOff; + + /// + /// The end offset of the input data. + /// + private int inputEnd; + + private readonly DeflaterPending pending; + private readonly DeflaterHuffman huffman; + + /// + /// The adler checksum + /// + private readonly Adler32 adler; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs index c79abc00a..4dc7210e8 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs @@ -1,175 +1,175 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// This is the DeflaterHuffman class. +/// +/// This class is not thread safe. This is inherent in the API, due +/// to the split of Deflate and SetInput. +/// +/// author of the original java version : Jochen Hoenicke +/// +public class DeflaterHuffman { - /// - /// This is the DeflaterHuffman class. - /// - /// This class is not thread safe. This is inherent in the API, due - /// to the split of Deflate and SetInput. - /// - /// author of the original java version : Jochen Hoenicke - /// - public class DeflaterHuffman - { - private const int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); - private const int LITERAL_NUM = 286; - - // Number of distance codes - private const int DIST_NUM = 30; - - // Number of codes used to transfer bit lengths - private const int BITLEN_NUM = 19; - - // repeat previous bit length 3-6 times (2 bits of repeat count) - private const int REP_3_6 = 16; - - // repeat a zero length 3-10 times (3 bits of repeat count) - private const int REP_3_10 = 17; - - // repeat a zero length 11-138 times (7 bits of repeat count) - private const int REP_11_138 = 18; - - private const int EOF_SYMBOL = 256; - - // The lengths of the bit length codes are sent in order of decreasing - // probability, to avoid transmitting the lengths for unused bit length codes. - private static readonly int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - - private static readonly byte[] bit4Reverse = { - 0, - 8, - 4, - 12, - 2, - 10, - 6, - 14, - 1, - 9, - 5, - 13, - 3, - 11, - 7, - 15 - }; - - private static short[] staticLCodes; - private static byte[] staticLLength; - private static short[] staticDCodes; - private static byte[] staticDLength; - - private class Tree - { - #region Instance Fields - - public short[] freqs; - - public byte[] length; - - public int minNumCodes; - - public int numCodes; - - private short[] codes; - private readonly int[] bl_counts; - private readonly int maxLength; - private DeflaterHuffman dh; - - #endregion Instance Fields - - #region Constructors - - public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength) - { - this.dh = dh; - this.minNumCodes = minCodes; - this.maxLength = maxLength; - freqs = new short[elems]; - bl_counts = new int[maxLength]; - } - - #endregion Constructors - - /// - /// Resets the internal state of the tree - /// - public void Reset() - { - for (int i = 0; i < freqs.Length; i++) - { - freqs[i] = 0; - } - codes = null; - length = null; - } - - public void WriteSymbol(int code) - { - // if (DeflaterConstants.DEBUGGING) { - // freqs[code]--; - // // Console.Write("writeSymbol("+freqs.length+","+code+"): "); - // } - dh.pending.WriteBits(codes[code] & 0xffff, length[code]); - } - - /// - /// Check that all frequencies are zero - /// - /// - /// At least one frequency is non-zero - /// - public void CheckEmpty() - { - bool empty = true; - for (int i = 0; i < freqs.Length; i++) - { - empty &= freqs[i] == 0; - } - - if (!empty) - { - throw new SharpZipBaseException("!Empty"); - } - } - - /// - /// Set static codes and length - /// - /// new codes - /// length for new codes - public void SetStaticCodes(short[] staticCodes, byte[] staticLengths) - { - codes = staticCodes; - length = staticLengths; - } - - /// - /// Build dynamic codes and lengths - /// - public void BuildCodes() - { - int numSymbols = freqs.Length; - int[] nextCode = new int[maxLength]; - int code = 0; - - codes = new short[freqs.Length]; - - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("buildCodes: "+freqs.Length); - // } - - for (int bits = 0; bits < maxLength; bits++) - { - nextCode[bits] = code; - code += bl_counts[bits] << (15 - bits); - - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("bits: " + ( bits + 1) + " count: " + bl_counts[bits] - // +" nextCode: "+code); - // } - } + private const int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); + private const int LITERAL_NUM = 286; + + // Number of distance codes + private const int DIST_NUM = 30; + + // Number of codes used to transfer bit lengths + private const int BITLEN_NUM = 19; + + // repeat previous bit length 3-6 times (2 bits of repeat count) + private const int REP_3_6 = 16; + + // repeat a zero length 3-10 times (3 bits of repeat count) + private const int REP_3_10 = 17; + + // repeat a zero length 11-138 times (7 bits of repeat count) + private const int REP_11_138 = 18; + + private const int EOF_SYMBOL = 256; + + // The lengths of the bit length codes are sent in order of decreasing + // probability, to avoid transmitting the lengths for unused bit length codes. + private static readonly int[] BL_ORDER = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + private static readonly byte[] bit4Reverse = { + 0, + 8, + 4, + 12, + 2, + 10, + 6, + 14, + 1, + 9, + 5, + 13, + 3, + 11, + 7, + 15 + }; + + private static readonly short[] staticLCodes; + private static readonly byte[] staticLLength; + private static readonly short[] staticDCodes; + private static readonly byte[] staticDLength; + + private class Tree + { + #region Instance Fields + + public short[] freqs; + + public byte[] length; + + public int minNumCodes; + + public int numCodes; + + private short[] codes; + private readonly int[] bl_counts; + private readonly int maxLength; + private readonly DeflaterHuffman dh; + + #endregion Instance Fields + + #region Constructors + + public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength) + { + this.dh = dh; + this.minNumCodes = minCodes; + this.maxLength = maxLength; + freqs = new short[elems]; + bl_counts = new int[maxLength]; + } + + #endregion Constructors + + /// + /// Resets the internal state of the tree + /// + public void Reset() + { + for (var i = 0; i < freqs.Length; i++) + { + freqs[i] = 0; + } + codes = null; + length = null; + } + + public void WriteSymbol(int code) + { + // if (DeflaterConstants.DEBUGGING) { + // freqs[code]--; + // // Console.Write("writeSymbol("+freqs.length+","+code+"): "); + // } + dh.pending.WriteBits(codes[code] & 0xffff, length[code]); + } + + /// + /// Check that all frequencies are zero + /// + /// + /// At least one frequency is non-zero + /// + public void CheckEmpty() + { + var empty = true; + for (var i = 0; i < freqs.Length; i++) + { + empty &= freqs[i] == 0; + } + + if (!empty) + { + throw new SharpZipBaseException("!Empty"); + } + } + + /// + /// Set static codes and length + /// + /// new codes + /// length for new codes + public void SetStaticCodes(short[] staticCodes, byte[] staticLengths) + { + codes = staticCodes; + length = staticLengths; + } + + /// + /// Build dynamic codes and lengths + /// + public void BuildCodes() + { + var numSymbols = freqs.Length; + var nextCode = new int[maxLength]; + var code = 0; + + codes = new short[freqs.Length]; + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("buildCodes: "+freqs.Length); + // } + + for (var bits = 0; bits < maxLength; bits++) + { + nextCode[bits] = code; + code += bl_counts[bits] << (15 - bits); + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("bits: " + ( bits + 1) + " count: " + bl_counts[bits] + // +" nextCode: "+code); + // } + } #if DebugDeflation if ( DeflaterConstants.DEBUGGING && (code != 65536) ) @@ -177,576 +177,576 @@ public void BuildCodes() throw new SharpZipBaseException("Inconsistent bl_counts!"); } #endif - for (int i = 0; i < numCodes; i++) - { - int bits = length[i]; - if (bits > 0) - { - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+"), - // +bits); - // } - - codes[i] = BitReverse(nextCode[bits - 1]); - nextCode[bits - 1] += 1 << (16 - bits); - } - } - } - - public void BuildTree() - { - int numSymbols = freqs.Length; - - /* heap is a priority queue, sorted by frequency, least frequent - * nodes first. The heap is a binary tree, with the property, that - * the parent node is smaller than both child nodes. This assures - * that the smallest node is the first parent. - * - * The binary tree is encoded in an array: 0 is root node and - * the nodes 2*n+1, 2*n+2 are the child nodes of node n. - */ - int[] heap = new int[numSymbols]; - int heapLen = 0; - int maxCode = 0; - for (int n = 0; n < numSymbols; n++) - { - int freq = freqs[n]; - if (freq != 0) - { - // Insert n into heap - int pos = heapLen++; - int ppos; - while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq) - { - heap[pos] = heap[ppos]; - pos = ppos; - } - heap[pos] = n; - - maxCode = n; - } - } - - /* We could encode a single literal with 0 bits but then we - * don't see the literals. Therefore we force at least two - * literals to avoid this case. We don't care about order in - * this case, both literals get a 1 bit code. - */ - while (heapLen < 2) - { - int node = maxCode < 2 ? ++maxCode : 0; - heap[heapLen++] = node; - } - - numCodes = Math.Max(maxCode + 1, minNumCodes); - - int numLeafs = heapLen; - int[] childs = new int[4 * heapLen - 2]; - int[] values = new int[2 * heapLen - 1]; - int numNodes = numLeafs; - for (int i = 0; i < heapLen; i++) - { - int node = heap[i]; - childs[2 * i] = node; - childs[2 * i + 1] = -1; - values[i] = freqs[node] << 8; - heap[i] = i; - } - - /* Construct the Huffman tree by repeatedly combining the least two - * frequent nodes. - */ - do - { - int first = heap[0]; - int last = heap[--heapLen]; - - // Propagate the hole to the leafs of the heap - int ppos = 0; - int path = 1; - - while (path < heapLen) - { - if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) - { - path++; - } - - heap[ppos] = heap[path]; - ppos = path; - path = path * 2 + 1; - } - - /* Now propagate the last element down along path. Normally - * it shouldn't go too deep. - */ - int lastVal = values[last]; - while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) - { - heap[path] = heap[ppos]; - } - heap[path] = last; - - int second = heap[0]; - - // Create a new node father of first and second - last = numNodes++; - childs[2 * last] = first; - childs[2 * last + 1] = second; - int mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff); - values[last] = lastVal = values[first] + values[second] - mindepth + 1; - - // Again, propagate the hole to the leafs - ppos = 0; - path = 1; - - while (path < heapLen) - { - if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) - { - path++; - } - - heap[ppos] = heap[path]; - ppos = path; - path = ppos * 2 + 1; - } - - // Now propagate the new element down along path - while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) - { - heap[path] = heap[ppos]; - } - heap[path] = last; - } while (heapLen > 1); - - if (heap[0] != childs.Length / 2 - 1) - { - throw new SharpZipBaseException("Heap invariant violated"); - } - - BuildLength(childs); - } - - /// - /// Get encoded length - /// - /// Encoded length, the sum of frequencies * lengths - public int GetEncodedLength() - { - int len = 0; - for (int i = 0; i < freqs.Length; i++) - { - len += freqs[i] * length[i]; - } - return len; - } - - /// - /// Scan a literal or distance tree to determine the frequencies of the codes - /// in the bit length tree. - /// - public void CalcBLFreq(Tree blTree) - { - int max_count; /* max repeat count */ - int min_count; /* min repeat count */ - int count; /* repeat count of the current code */ - int curlen = -1; /* length of current code */ - - int i = 0; - while (i < numCodes) - { - count = 1; - int nextlen = length[i]; - if (nextlen == 0) - { - max_count = 138; - min_count = 3; - } - else - { - max_count = 6; - min_count = 3; - if (curlen != nextlen) - { - blTree.freqs[nextlen]++; - count = 0; - } - } - curlen = nextlen; - i++; - - while (i < numCodes && curlen == length[i]) - { - i++; - if (++count >= max_count) - { - break; - } - } - - if (count < min_count) - { - blTree.freqs[curlen] += (short)count; - } - else if (curlen != 0) - { - blTree.freqs[REP_3_6]++; - } - else if (count <= 10) - { - blTree.freqs[REP_3_10]++; - } - else - { - blTree.freqs[REP_11_138]++; - } - } - } - - /// - /// Write tree values - /// - /// Tree to write - public void WriteTree(Tree blTree) - { - int max_count; // max repeat count - int min_count; // min repeat count - int count; // repeat count of the current code - int curlen = -1; // length of current code - - int i = 0; - while (i < numCodes) - { - count = 1; - int nextlen = length[i]; - if (nextlen == 0) - { - max_count = 138; - min_count = 3; - } - else - { - max_count = 6; - min_count = 3; - if (curlen != nextlen) - { - blTree.WriteSymbol(nextlen); - count = 0; - } - } - curlen = nextlen; - i++; - - while (i < numCodes && curlen == length[i]) - { - i++; - if (++count >= max_count) - { - break; - } - } - - if (count < min_count) - { - while (count-- > 0) - { - blTree.WriteSymbol(curlen); - } - } - else if (curlen != 0) - { - blTree.WriteSymbol(REP_3_6); - dh.pending.WriteBits(count - 3, 2); - } - else if (count <= 10) - { - blTree.WriteSymbol(REP_3_10); - dh.pending.WriteBits(count - 3, 3); - } - else - { - blTree.WriteSymbol(REP_11_138); - dh.pending.WriteBits(count - 11, 7); - } - } - } - - private void BuildLength(int[] childs) - { - this.length = new byte[freqs.Length]; - int numNodes = childs.Length / 2; - int numLeafs = (numNodes + 1) / 2; - int overflow = 0; - - for (int i = 0; i < maxLength; i++) - { - bl_counts[i] = 0; - } - - // First calculate optimal bit lengths - int[] lengths = new int[numNodes]; - lengths[numNodes - 1] = 0; - - for (int i = numNodes - 1; i >= 0; i--) - { - if (childs[2 * i + 1] != -1) - { - int bitLength = lengths[i] + 1; - if (bitLength > maxLength) - { - bitLength = maxLength; - overflow++; - } - lengths[childs[2 * i]] = lengths[childs[2 * i + 1]] = bitLength; - } - else - { - // A leaf node - int bitLength = lengths[i]; - bl_counts[bitLength - 1]++; - this.length[childs[2 * i]] = (byte)lengths[i]; - } - } - - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("Tree "+freqs.Length+" lengths:"); - // for (int i=0; i < numLeafs; i++) { - // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] - // + " len: "+length[childs[2*i]]); - // } - // } - - if (overflow == 0) - { - return; - } - - int incrBitLen = maxLength - 1; - do - { - // Find the first bit length which could increase: - while (bl_counts[--incrBitLen] == 0) - { - } - - // Move this node one down and remove a corresponding - // number of overflow nodes. - do - { - bl_counts[incrBitLen]--; - bl_counts[++incrBitLen]++; - overflow -= 1 << (maxLength - 1 - incrBitLen); - } while (overflow > 0 && incrBitLen < maxLength - 1); - } while (overflow > 0); - - /* We may have overshot above. Move some nodes from maxLength to - * maxLength-1 in that case. - */ - bl_counts[maxLength - 1] += overflow; - bl_counts[maxLength - 2] -= overflow; - - /* Now recompute all bit lengths, scanning in increasing - * frequency. It is simpler to reconstruct all lengths instead of - * fixing only the wrong ones. This idea is taken from 'ar' - * written by Haruhiko Okumura. - * - * The nodes were inserted with decreasing frequency into the childs - * array. - */ - int nodePtr = 2 * numLeafs; - for (int bits = maxLength; bits != 0; bits--) - { - int n = bl_counts[bits - 1]; - while (n > 0) - { - int childPtr = 2 * childs[nodePtr++]; - if (childs[childPtr + 1] == -1) - { - // We found another leaf - length[childs[childPtr]] = (byte)bits; - n--; - } - } - } - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("*** After overflow elimination. ***"); - // for (int i=0; i < numLeafs; i++) { - // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] - // + " len: "+length[childs[2*i]]); - // } - // } - } - } - - #region Instance Fields - - /// - /// Pending buffer to use - /// - public DeflaterPending pending; - - private Tree literalTree; - private Tree distTree; - private Tree blTree; - - // Buffer for distances - private short[] d_buf; - - private byte[] l_buf; - private int last_lit; - private int extra_bits; - - #endregion Instance Fields - - static DeflaterHuffman() - { - // See RFC 1951 3.2.6 - // Literal codes - staticLCodes = new short[LITERAL_NUM]; - staticLLength = new byte[LITERAL_NUM]; - - int i = 0; - while (i < 144) - { - staticLCodes[i] = BitReverse((0x030 + i) << 8); - staticLLength[i++] = 8; - } - - while (i < 256) - { - staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); - staticLLength[i++] = 9; - } - - while (i < 280) - { - staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); - staticLLength[i++] = 7; - } - - while (i < LITERAL_NUM) - { - staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); - staticLLength[i++] = 8; - } - - // Distance codes - staticDCodes = new short[DIST_NUM]; - staticDLength = new byte[DIST_NUM]; - for (i = 0; i < DIST_NUM; i++) - { - staticDCodes[i] = BitReverse(i << 11); - staticDLength[i] = 5; - } - } - - /// - /// Construct instance with pending buffer - /// - /// Pending buffer to use - public DeflaterHuffman(DeflaterPending pending) - { - this.pending = pending; - - literalTree = new Tree(this, LITERAL_NUM, 257, 15); - distTree = new Tree(this, DIST_NUM, 1, 15); - blTree = new Tree(this, BITLEN_NUM, 4, 7); - - d_buf = new short[BUFSIZE]; - l_buf = new byte[BUFSIZE]; - } - - /// - /// Reset internal state - /// - public void Reset() - { - last_lit = 0; - extra_bits = 0; - literalTree.Reset(); - distTree.Reset(); - blTree.Reset(); - } - - /// - /// Write all trees to pending buffer - /// - /// The number/rank of treecodes to send. - public void SendAllTrees(int blTreeCodes) - { - blTree.BuildCodes(); - literalTree.BuildCodes(); - distTree.BuildCodes(); - pending.WriteBits(literalTree.numCodes - 257, 5); - pending.WriteBits(distTree.numCodes - 1, 5); - pending.WriteBits(blTreeCodes - 4, 4); - for (int rank = 0; rank < blTreeCodes; rank++) - { - pending.WriteBits(blTree.length[BL_ORDER[rank]], 3); - } - literalTree.WriteTree(blTree); - distTree.WriteTree(blTree); + for (var i = 0; i < numCodes; i++) + { + int bits = length[i]; + if (bits > 0) + { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("codes["+i+"] = rev(" + nextCode[bits-1]+"), + // +bits); + // } + + codes[i] = BitReverse(nextCode[bits - 1]); + nextCode[bits - 1] += 1 << (16 - bits); + } + } + } + + public void BuildTree() + { + var numSymbols = freqs.Length; + + /* heap is a priority queue, sorted by frequency, least frequent + * nodes first. The heap is a binary tree, with the property, that + * the parent node is smaller than both child nodes. This assures + * that the smallest node is the first parent. + * + * The binary tree is encoded in an array: 0 is root node and + * the nodes 2*n+1, 2*n+2 are the child nodes of node n. + */ + var heap = new int[numSymbols]; + var heapLen = 0; + var maxCode = 0; + for (var n = 0; n < numSymbols; n++) + { + int freq = freqs[n]; + if (freq != 0) + { + // Insert n into heap + var pos = heapLen++; + int ppos; + while (pos > 0 && freqs[heap[ppos = (pos - 1) / 2]] > freq) + { + heap[pos] = heap[ppos]; + pos = ppos; + } + heap[pos] = n; + + maxCode = n; + } + } + + /* We could encode a single literal with 0 bits but then we + * don't see the literals. Therefore we force at least two + * literals to avoid this case. We don't care about order in + * this case, both literals get a 1 bit code. + */ + while (heapLen < 2) + { + var node = maxCode < 2 ? ++maxCode : 0; + heap[heapLen++] = node; + } + + numCodes = Math.Max(maxCode + 1, minNumCodes); + + var numLeafs = heapLen; + var childs = new int[(4 * heapLen) - 2]; + var values = new int[(2 * heapLen) - 1]; + var numNodes = numLeafs; + for (var i = 0; i < heapLen; i++) + { + var node = heap[i]; + childs[2 * i] = node; + childs[(2 * i) + 1] = -1; + values[i] = freqs[node] << 8; + heap[i] = i; + } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + do + { + var first = heap[0]; + var last = heap[--heapLen]; + + // Propagate the hole to the leafs of the heap + var ppos = 0; + var path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) + { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = (path * 2) + 1; + } + + /* Now propagate the last element down along path. Normally + * it shouldn't go too deep. + */ + var lastVal = values[last]; + while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) + { + heap[path] = heap[ppos]; + } + heap[path] = last; + + var second = heap[0]; + + // Create a new node father of first and second + last = numNodes++; + childs[2 * last] = first; + childs[(2 * last) + 1] = second; + var mindepth = Math.Min(values[first] & 0xff, values[second] & 0xff); + values[last] = lastVal = values[first] + values[second] - mindepth + 1; + + // Again, propagate the hole to the leafs + ppos = 0; + path = 1; + + while (path < heapLen) + { + if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]]) + { + path++; + } + + heap[ppos] = heap[path]; + ppos = path; + path = (ppos * 2) + 1; + } + + // Now propagate the new element down along path + while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal) + { + heap[path] = heap[ppos]; + } + heap[path] = last; + } while (heapLen > 1); + + if (heap[0] != (childs.Length / 2) - 1) + { + throw new SharpZipBaseException("Heap invariant violated"); + } + + BuildLength(childs); + } + + /// + /// Get encoded length + /// + /// Encoded length, the sum of frequencies * lengths + public int GetEncodedLength() + { + var len = 0; + for (var i = 0; i < freqs.Length; i++) + { + len += freqs[i] * length[i]; + } + return len; + } + + /// + /// Scan a literal or distance tree to determine the frequencies of the codes + /// in the bit length tree. + /// + public void CalcBLFreq(Tree blTree) + { + int max_count; /* max repeat count */ + int min_count; /* min repeat count */ + int count; /* repeat count of the current code */ + var curlen = -1; /* length of current code */ + + var i = 0; + while (i < numCodes) + { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) + { + max_count = 138; + min_count = 3; + } + else + { + max_count = 6; + min_count = 3; + if (curlen != nextlen) + { + blTree.freqs[nextlen]++; + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) + { + i++; + if (++count >= max_count) + { + break; + } + } + + if (count < min_count) + { + blTree.freqs[curlen] += (short)count; + } + else if (curlen != 0) + { + blTree.freqs[REP_3_6]++; + } + else if (count <= 10) + { + blTree.freqs[REP_3_10]++; + } + else + { + blTree.freqs[REP_11_138]++; + } + } + } + + /// + /// Write tree values + /// + /// Tree to write + public void WriteTree(Tree blTree) + { + int max_count; // max repeat count + int min_count; // min repeat count + int count; // repeat count of the current code + var curlen = -1; // length of current code + + var i = 0; + while (i < numCodes) + { + count = 1; + int nextlen = length[i]; + if (nextlen == 0) + { + max_count = 138; + min_count = 3; + } + else + { + max_count = 6; + min_count = 3; + if (curlen != nextlen) + { + blTree.WriteSymbol(nextlen); + count = 0; + } + } + curlen = nextlen; + i++; + + while (i < numCodes && curlen == length[i]) + { + i++; + if (++count >= max_count) + { + break; + } + } + + if (count < min_count) + { + while (count-- > 0) + { + blTree.WriteSymbol(curlen); + } + } + else if (curlen != 0) + { + blTree.WriteSymbol(REP_3_6); + dh.pending.WriteBits(count - 3, 2); + } + else if (count <= 10) + { + blTree.WriteSymbol(REP_3_10); + dh.pending.WriteBits(count - 3, 3); + } + else + { + blTree.WriteSymbol(REP_11_138); + dh.pending.WriteBits(count - 11, 7); + } + } + } + + private void BuildLength(int[] childs) + { + this.length = new byte[freqs.Length]; + var numNodes = childs.Length / 2; + var numLeafs = (numNodes + 1) / 2; + var overflow = 0; + + for (var i = 0; i < maxLength; i++) + { + bl_counts[i] = 0; + } + + // First calculate optimal bit lengths + var lengths = new int[numNodes]; + lengths[numNodes - 1] = 0; + + for (var i = numNodes - 1; i >= 0; i--) + { + if (childs[(2 * i) + 1] != -1) + { + var bitLength = lengths[i] + 1; + if (bitLength > maxLength) + { + bitLength = maxLength; + overflow++; + } + lengths[childs[2 * i]] = lengths[childs[(2 * i) + 1]] = bitLength; + } + else + { + // A leaf node + var bitLength = lengths[i]; + bl_counts[bitLength - 1]++; + this.length[childs[2 * i]] = (byte)lengths[i]; + } + } + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Tree "+freqs.Length+" lengths:"); + // for (int i=0; i < numLeafs; i++) { + // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] + // + " len: "+length[childs[2*i]]); + // } + // } + + if (overflow == 0) + { + return; + } + + var incrBitLen = maxLength - 1; + do + { + // Find the first bit length which could increase: + while (bl_counts[--incrBitLen] == 0) + { + } + + // Move this node one down and remove a corresponding + // number of overflow nodes. + do + { + bl_counts[incrBitLen]--; + bl_counts[++incrBitLen]++; + overflow -= 1 << (maxLength - 1 - incrBitLen); + } while (overflow > 0 && incrBitLen < maxLength - 1); + } while (overflow > 0); + + /* We may have overshot above. Move some nodes from maxLength to + * maxLength-1 in that case. + */ + bl_counts[maxLength - 1] += overflow; + bl_counts[maxLength - 2] -= overflow; + + /* Now recompute all bit lengths, scanning in increasing + * frequency. It is simpler to reconstruct all lengths instead of + * fixing only the wrong ones. This idea is taken from 'ar' + * written by Haruhiko Okumura. + * + * The nodes were inserted with decreasing frequency into the childs + * array. + */ + var nodePtr = 2 * numLeafs; + for (var bits = maxLength; bits != 0; bits--) + { + var n = bl_counts[bits - 1]; + while (n > 0) + { + var childPtr = 2 * childs[nodePtr++]; + if (childs[childPtr + 1] == -1) + { + // We found another leaf + length[childs[childPtr]] = (byte)bits; + n--; + } + } + } + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("*** After overflow elimination. ***"); + // for (int i=0; i < numLeafs; i++) { + // //Console.WriteLine("Node "+childs[2*i]+" freq: "+freqs[childs[2*i]] + // + " len: "+length[childs[2*i]]); + // } + // } + } + } + + #region Instance Fields + + /// + /// Pending buffer to use + /// + public DeflaterPending pending; + + private readonly Tree literalTree; + private readonly Tree distTree; + private readonly Tree blTree; + + // Buffer for distances + private readonly short[] d_buf; + + private readonly byte[] l_buf; + private int last_lit; + private int extra_bits; + + #endregion Instance Fields + + static DeflaterHuffman() + { + // See RFC 1951 3.2.6 + // Literal codes + staticLCodes = new short[LITERAL_NUM]; + staticLLength = new byte[LITERAL_NUM]; + + var i = 0; + while (i < 144) + { + staticLCodes[i] = BitReverse((0x030 + i) << 8); + staticLLength[i++] = 8; + } + + while (i < 256) + { + staticLCodes[i] = BitReverse((0x190 - 144 + i) << 7); + staticLLength[i++] = 9; + } + + while (i < 280) + { + staticLCodes[i] = BitReverse((0x000 - 256 + i) << 9); + staticLLength[i++] = 7; + } + + while (i < LITERAL_NUM) + { + staticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8); + staticLLength[i++] = 8; + } + + // Distance codes + staticDCodes = new short[DIST_NUM]; + staticDLength = new byte[DIST_NUM]; + for (i = 0; i < DIST_NUM; i++) + { + staticDCodes[i] = BitReverse(i << 11); + staticDLength[i] = 5; + } + } + + /// + /// Construct instance with pending buffer + /// + /// Pending buffer to use + public DeflaterHuffman(DeflaterPending pending) + { + this.pending = pending; + + literalTree = new Tree(this, LITERAL_NUM, 257, 15); + distTree = new Tree(this, DIST_NUM, 1, 15); + blTree = new Tree(this, BITLEN_NUM, 4, 7); + + d_buf = new short[BUFSIZE]; + l_buf = new byte[BUFSIZE]; + } + + /// + /// Reset internal state + /// + public void Reset() + { + last_lit = 0; + extra_bits = 0; + literalTree.Reset(); + distTree.Reset(); + blTree.Reset(); + } + + /// + /// Write all trees to pending buffer + /// + /// The number/rank of treecodes to send. + public void SendAllTrees(int blTreeCodes) + { + blTree.BuildCodes(); + literalTree.BuildCodes(); + distTree.BuildCodes(); + pending.WriteBits(literalTree.numCodes - 257, 5); + pending.WriteBits(distTree.numCodes - 1, 5); + pending.WriteBits(blTreeCodes - 4, 4); + for (var rank = 0; rank < blTreeCodes; rank++) + { + pending.WriteBits(blTree.length[BL_ORDER[rank]], 3); + } + literalTree.WriteTree(blTree); + distTree.WriteTree(blTree); #if DebugDeflation if (DeflaterConstants.DEBUGGING) { blTree.CheckEmpty(); } #endif - } - - /// - /// Compress current buffer writing data to pending buffer - /// - public void CompressBlock() - { - for (int i = 0; i < last_lit; i++) - { - int litlen = l_buf[i] & 0xff; - int dist = d_buf[i]; - if (dist-- != 0) - { - // if (DeflaterConstants.DEBUGGING) { - // Console.Write("["+(dist+1)+","+(litlen+3)+"]: "); - // } - - int lc = Lcode(litlen); - literalTree.WriteSymbol(lc); - - int bits = (lc - 261) / 4; - if (bits > 0 && bits <= 5) - { - pending.WriteBits(litlen & ((1 << bits) - 1), bits); - } - - int dc = Dcode(dist); - distTree.WriteSymbol(dc); - - bits = dc / 2 - 1; - if (bits > 0) - { - pending.WriteBits(dist & ((1 << bits) - 1), bits); - } - } - else - { - // if (DeflaterConstants.DEBUGGING) { - // if (litlen > 32 && litlen < 127) { - // Console.Write("("+(char)litlen+"): "); - // } else { - // Console.Write("{"+litlen+"}: "); - // } - // } - literalTree.WriteSymbol(litlen); - } - } + } + + /// + /// Compress current buffer writing data to pending buffer + /// + public void CompressBlock() + { + for (var i = 0; i < last_lit; i++) + { + var litlen = l_buf[i] & 0xff; + int dist = d_buf[i]; + if (dist-- != 0) + { + // if (DeflaterConstants.DEBUGGING) { + // Console.Write("["+(dist+1)+","+(litlen+3)+"]: "); + // } + + var lc = Lcode(litlen); + literalTree.WriteSymbol(lc); + + var bits = (lc - 261) / 4; + if (bits is > 0 and <= 5) + { + pending.WriteBits(litlen & ((1 << bits) - 1), bits); + } + + var dc = Dcode(dist); + distTree.WriteSymbol(dc); + + bits = (dc / 2) - 1; + if (bits > 0) + { + pending.WriteBits(dist & ((1 << bits) - 1), bits); + } + } + else + { + // if (DeflaterConstants.DEBUGGING) { + // if (litlen > 32 && litlen < 127) { + // Console.Write("("+(char)litlen+"): "); + // } else { + // Console.Write("{"+litlen+"}: "); + // } + // } + literalTree.WriteSymbol(litlen); + } + } #if DebugDeflation if (DeflaterConstants.DEBUGGING) { Console.Write("EOF: "); } #endif - literalTree.WriteSymbol(EOF_SYMBOL); + literalTree.WriteSymbol(EOF_SYMBOL); #if DebugDeflation if (DeflaterConstants.DEBUGGING) { @@ -754,206 +754,205 @@ public void CompressBlock() distTree.CheckEmpty(); } #endif - } - - /// - /// Flush block to output with no compression - /// - /// Data to write - /// Index of first byte to write - /// Count of bytes to write - /// True if this is the last block - public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) - { + } + + /// + /// Flush block to output with no compression + /// + /// Data to write + /// Index of first byte to write + /// Count of bytes to write + /// True if this is the last block + public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { #if DebugDeflation // if (DeflaterConstants.DEBUGGING) { // //Console.WriteLine("Flushing stored block "+ storedLength); // } #endif - pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); - pending.AlignToByte(); - pending.WriteShort(storedLength); - pending.WriteShort(~storedLength); - pending.WriteBlock(stored, storedOffset, storedLength); - Reset(); - } - - /// - /// Flush block to output with compression - /// - /// Data to flush - /// Index of first byte to flush - /// Count of bytes to flush - /// True if this is the last block - public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) - { - literalTree.freqs[EOF_SYMBOL]++; - - // Build trees - literalTree.BuildTree(); - distTree.BuildTree(); - - // Calculate bitlen frequency - literalTree.CalcBLFreq(blTree); - distTree.CalcBLFreq(blTree); - - // Build bitlen tree - blTree.BuildTree(); - - int blTreeCodes = 4; - for (int i = 18; i > blTreeCodes; i--) - { - if (blTree.length[BL_ORDER[i]] > 0) - { - blTreeCodes = i + 1; - } - } - int opt_len = 14 + blTreeCodes * 3 + blTree.GetEncodedLength() + - literalTree.GetEncodedLength() + distTree.GetEncodedLength() + - extra_bits; - - int static_len = extra_bits; - for (int i = 0; i < LITERAL_NUM; i++) - { - static_len += literalTree.freqs[i] * staticLLength[i]; - } - for (int i = 0; i < DIST_NUM; i++) - { - static_len += distTree.freqs[i] * staticDLength[i]; - } - if (opt_len >= static_len) - { - // Force static trees - opt_len = static_len; - } - - if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) - { - // Store Block - - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("Storing, since " + storedLength + " < " + opt_len - // + " <= " + static_len); - // } - FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); - } - else if (opt_len == static_len) - { - // Encode with static tree - pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); - literalTree.SetStaticCodes(staticLCodes, staticLLength); - distTree.SetStaticCodes(staticDCodes, staticDLength); - CompressBlock(); - Reset(); - } - else - { - // Encode with dynamic tree - pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); - SendAllTrees(blTreeCodes); - CompressBlock(); - Reset(); - } - } - - /// - /// Get value indicating if internal buffer is full - /// - /// true if buffer is full - public bool IsFull() - { - return last_lit >= BUFSIZE; - } - - /// - /// Add literal to buffer - /// - /// Literal value to add to buffer. - /// Value indicating internal buffer is full - public bool TallyLit(int literal) - { - // if (DeflaterConstants.DEBUGGING) { - // if (lit > 32 && lit < 127) { - // //Console.WriteLine("("+(char)lit+")"); - // } else { - // //Console.WriteLine("{"+lit+"}"); - // } - // } - d_buf[last_lit] = 0; - l_buf[last_lit++] = (byte)literal; - literalTree.freqs[literal]++; - return IsFull(); - } - - /// - /// Add distance code and length to literal and distance trees - /// - /// Distance code - /// Length - /// Value indicating if internal buffer is full - public bool TallyDist(int distance, int length) - { - // if (DeflaterConstants.DEBUGGING) { - // //Console.WriteLine("[" + distance + "," + length + "]"); - // } - - d_buf[last_lit] = (short)distance; - l_buf[last_lit++] = (byte)(length - 3); - - int lc = Lcode(length - 3); - literalTree.freqs[lc]++; - if (lc >= 265 && lc < 285) - { - extra_bits += (lc - 261) / 4; - } - - int dc = Dcode(distance - 1); - distTree.freqs[dc]++; - if (dc >= 4) - { - extra_bits += dc / 2 - 1; - } - return IsFull(); - } - - /// - /// Reverse the bits of a 16 bit value. - /// - /// Value to reverse bits - /// Value with bits reversed - public static short BitReverse(int toReverse) - { - return (short)(bit4Reverse[toReverse & 0xF] << 12 | - bit4Reverse[(toReverse >> 4) & 0xF] << 8 | - bit4Reverse[(toReverse >> 8) & 0xF] << 4 | - bit4Reverse[toReverse >> 12]); - } - - private static int Lcode(int length) - { - if (length == 255) - { - return 285; - } - - int code = 257; - while (length >= 8) - { - code += 4; - length >>= 1; - } - return code + length; - } - - private static int Dcode(int distance) - { - int code = 0; - while (distance >= 4) - { - code += 2; - distance >>= 1; - } - return code + distance; - } - } + pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); + pending.AlignToByte(); + pending.WriteShort(storedLength); + pending.WriteShort(~storedLength); + pending.WriteBlock(stored, storedOffset, storedLength); + Reset(); + } + + /// + /// Flush block to output with compression + /// + /// Data to flush + /// Index of first byte to flush + /// Count of bytes to flush + /// True if this is the last block + public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) + { + literalTree.freqs[EOF_SYMBOL]++; + + // Build trees + literalTree.BuildTree(); + distTree.BuildTree(); + + // Calculate bitlen frequency + literalTree.CalcBLFreq(blTree); + distTree.CalcBLFreq(blTree); + + // Build bitlen tree + blTree.BuildTree(); + + var blTreeCodes = 4; + for (var i = 18; i > blTreeCodes; i--) + { + if (blTree.length[BL_ORDER[i]] > 0) + { + blTreeCodes = i + 1; + } + } + var opt_len = 14 + (blTreeCodes * 3) + blTree.GetEncodedLength() + + literalTree.GetEncodedLength() + distTree.GetEncodedLength() + + extra_bits; + + var static_len = extra_bits; + for (var i = 0; i < LITERAL_NUM; i++) + { + static_len += literalTree.freqs[i] * staticLLength[i]; + } + for (var i = 0; i < DIST_NUM; i++) + { + static_len += distTree.freqs[i] * staticDLength[i]; + } + if (opt_len >= static_len) + { + // Force static trees + opt_len = static_len; + } + + if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) + { + // Store Block + + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("Storing, since " + storedLength + " < " + opt_len + // + " <= " + static_len); + // } + FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); + } + else if (opt_len == static_len) + { + // Encode with static tree + pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); + literalTree.SetStaticCodes(staticLCodes, staticLLength); + distTree.SetStaticCodes(staticDCodes, staticDLength); + CompressBlock(); + Reset(); + } + else + { + // Encode with dynamic tree + pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); + SendAllTrees(blTreeCodes); + CompressBlock(); + Reset(); + } + } + + /// + /// Get value indicating if internal buffer is full + /// + /// true if buffer is full + public bool IsFull() + { + return last_lit >= BUFSIZE; + } + + /// + /// Add literal to buffer + /// + /// Literal value to add to buffer. + /// Value indicating internal buffer is full + public bool TallyLit(int literal) + { + // if (DeflaterConstants.DEBUGGING) { + // if (lit > 32 && lit < 127) { + // //Console.WriteLine("("+(char)lit+")"); + // } else { + // //Console.WriteLine("{"+lit+"}"); + // } + // } + d_buf[last_lit] = 0; + l_buf[last_lit++] = (byte)literal; + literalTree.freqs[literal]++; + return IsFull(); + } + + /// + /// Add distance code and length to literal and distance trees + /// + /// Distance code + /// Length + /// Value indicating if internal buffer is full + public bool TallyDist(int distance, int length) + { + // if (DeflaterConstants.DEBUGGING) { + // //Console.WriteLine("[" + distance + "," + length + "]"); + // } + + d_buf[last_lit] = (short)distance; + l_buf[last_lit++] = (byte)(length - 3); + + var lc = Lcode(length - 3); + literalTree.freqs[lc]++; + if (lc is >= 265 and < 285) + { + extra_bits += (lc - 261) / 4; + } + + var dc = Dcode(distance - 1); + distTree.freqs[dc]++; + if (dc >= 4) + { + extra_bits += (dc / 2) - 1; + } + return IsFull(); + } + + /// + /// Reverse the bits of a 16 bit value. + /// + /// Value to reverse bits + /// Value with bits reversed + public static short BitReverse(int toReverse) + { + return (short)((bit4Reverse[toReverse & 0xF] << 12) | + (bit4Reverse[(toReverse >> 4) & 0xF] << 8) | + (bit4Reverse[(toReverse >> 8) & 0xF] << 4) | + bit4Reverse[toReverse >> 12]); + } + + private static int Lcode(int length) + { + if (length == 255) + { + return 285; + } + + var code = 257; + while (length >= 8) + { + code += 4; + length >>= 1; + } + return code + length; + } + + private static int Dcode(int distance) + { + var code = 0; + while (distance >= 4) + { + code += 2; + distance >>= 1; + } + return code + distance; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs index 206235297..6e6996cd2 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs @@ -1,17 +1,16 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// This class stores the pending output of the Deflater. +/// +/// author of the original java version : Jochen Hoenicke +/// +public class DeflaterPending : PendingBuffer { - /// - /// This class stores the pending output of the Deflater. - /// - /// author of the original java version : Jochen Hoenicke - /// - public class DeflaterPending : PendingBuffer - { - /// - /// Construct instance with default buffer size - /// - public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) - { - } - } + /// + /// Construct instance with default buffer size + /// + public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs index 0a2c74cfb..9f6f2efa6 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs @@ -2,886 +2,876 @@ using MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// Inflater is used to decompress data that has been compressed according +/// to the "deflate" standard described in rfc1951. +/// +/// By default Zlib (rfc1950) headers and footers are expected in the input. +/// You can use constructor public Inflater(bool noHeader) passing true +/// if there is no Zlib header information +/// +/// The usage is as following. First you have to set some input with +/// SetInput(), then Inflate() it. If inflate doesn't +/// inflate any bytes there may be three reasons: +///
    +///
  • IsNeedingInput() returns true because the input buffer is empty. +/// You have to provide more input with SetInput(). +/// NOTE: IsNeedingInput() also returns true when, the stream is finished. +///
  • +///
  • IsNeedingDictionary() returns true, you have to provide a preset +/// dictionary with SetDictionary().
  • +///
  • IsFinished returns true, the inflater has finished.
  • +///
+/// Once the first output byte is produced, a dictionary will not be +/// needed at a later stage. +/// +/// author of the original java version : John Leuner, Jochen Hoenicke +///
+public class Inflater { - /// - /// Inflater is used to decompress data that has been compressed according - /// to the "deflate" standard described in rfc1951. - /// - /// By default Zlib (rfc1950) headers and footers are expected in the input. - /// You can use constructor public Inflater(bool noHeader) passing true - /// if there is no Zlib header information - /// - /// The usage is as following. First you have to set some input with - /// SetInput(), then Inflate() it. If inflate doesn't - /// inflate any bytes there may be three reasons: - ///
    - ///
  • IsNeedingInput() returns true because the input buffer is empty. - /// You have to provide more input with SetInput(). - /// NOTE: IsNeedingInput() also returns true when, the stream is finished. - ///
  • - ///
  • IsNeedingDictionary() returns true, you have to provide a preset - /// dictionary with SetDictionary().
  • - ///
  • IsFinished returns true, the inflater has finished.
  • - ///
- /// Once the first output byte is produced, a dictionary will not be - /// needed at a later stage. - /// - /// author of the original java version : John Leuner, Jochen Hoenicke - ///
- public class Inflater - { - #region Constants/Readonly - - /// - /// Copy lengths for literal codes 257..285 - /// - private static readonly int[] CPLENS = { - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 - }; - - /// - /// Extra bits for literal codes 257..285 - /// - private static readonly int[] CPLEXT = { - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, - 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 - }; - - /// - /// Copy offsets for distance codes 0..29 - /// - private static readonly int[] CPDIST = { - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577 - }; - - /// - /// Extra bits for distance codes - /// - private static readonly int[] CPDEXT = { - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, - 12, 12, 13, 13 - }; - - /// - /// These are the possible states for an inflater - /// - private const int DECODE_HEADER = 0; - - private const int DECODE_DICT = 1; - private const int DECODE_BLOCKS = 2; - private const int DECODE_STORED_LEN1 = 3; - private const int DECODE_STORED_LEN2 = 4; - private const int DECODE_STORED = 5; - private const int DECODE_DYN_HEADER = 6; - private const int DECODE_HUFFMAN = 7; - private const int DECODE_HUFFMAN_LENBITS = 8; - private const int DECODE_HUFFMAN_DIST = 9; - private const int DECODE_HUFFMAN_DISTBITS = 10; - private const int DECODE_CHKSUM = 11; - private const int FINISHED = 12; - - #endregion Constants/Readonly - - #region Instance Fields - - /// - /// This variable contains the current state. - /// - private int mode; - - /// - /// The adler checksum of the dictionary or of the decompressed - /// stream, as it is written in the header resp. footer of the - /// compressed stream. - /// Only valid if mode is DECODE_DICT or DECODE_CHKSUM. - /// - private int readAdler; - - /// - /// The number of bits needed to complete the current state. This - /// is valid, if mode is DECODE_DICT, DECODE_CHKSUM, - /// DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. - /// - private int neededBits; - - private int repLength; - private int repDist; - private int uncomprLen; - - /// - /// True, if the last block flag was set in the last block of the - /// inflated stream. This means that the stream ends after the - /// current block. - /// - private bool isLastBlock; - - /// - /// The total number of inflated bytes. - /// - private long totalOut; - - /// - /// The total number of bytes set with setInput(). This is not the - /// value returned by the TotalIn property, since this also includes the - /// unprocessed input. - /// - private long totalIn; - - /// - /// This variable stores the noHeader flag that was given to the constructor. - /// True means, that the inflated stream doesn't contain a Zlib header or - /// footer. - /// - private bool noHeader; - - private readonly StreamManipulator input; - private OutputWindow outputWindow; - private InflaterDynHeader dynHeader; - private InflaterHuffmanTree litlenTree, distTree; - private Adler32 adler; - - #endregion Instance Fields - - #region Constructors - - /// - /// Creates a new inflater or RFC1951 decompressor - /// RFC1950/Zlib headers and footers will be expected in the input data - /// - public Inflater() : this(false) - { - } - - /// - /// Creates a new inflater. - /// - /// - /// True if no RFC1950/Zlib header and footer fields are expected in the input data - /// - /// This is used for GZIPed/Zipped input. - /// - /// For compatibility with - /// Sun JDK you should provide one byte of input more than needed in - /// this case. - /// - public Inflater(bool noHeader) - { - this.noHeader = noHeader; - if (!noHeader) - this.adler = new Adler32(); - input = new StreamManipulator(); - outputWindow = new OutputWindow(); - mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; - } - - #endregion Constructors - - /// - /// Resets the inflater so that a new stream can be decompressed. All - /// pending input and output will be discarded. - /// - public void Reset() - { - mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; - totalIn = 0; - totalOut = 0; - input.Reset(); - outputWindow.Reset(); - dynHeader = null; - litlenTree = null; - distTree = null; - isLastBlock = false; - adler?.Reset(); - } - - /// - /// Decodes a zlib/RFC1950 header. - /// - /// - /// False if more input is needed. - /// - /// - /// The header is invalid. - /// - private bool DecodeHeader() - { - int header = input.PeekBits(16); - if (header < 0) - { - return false; - } - input.DropBits(16); - - // The header is written in "wrong" byte order - header = ((header << 8) | (header >> 8)) & 0xffff; - if (header % 31 != 0) - { - throw new SharpZipBaseException("Header checksum illegal"); - } - - if ((header & 0x0f00) != (Deflater.DEFLATED << 8)) - { - throw new SharpZipBaseException("Compression Method unknown"); - } - - /* Maximum size of the backwards window in bits. - * We currently ignore this, but we could use it to make the - * inflater window more space efficient. On the other hand the - * full window (15 bits) is needed most times, anyway. - int max_wbits = ((header & 0x7000) >> 12) + 8; - */ - - if ((header & 0x0020) == 0) - { // Dictionary flag? - mode = DECODE_BLOCKS; - } - else - { - mode = DECODE_DICT; - neededBits = 32; - } - return true; - } - - /// - /// Decodes the dictionary checksum after the deflate header. - /// - /// - /// False if more input is needed. - /// - private bool DecodeDict() - { - while (neededBits > 0) - { - int dictByte = input.PeekBits(8); - if (dictByte < 0) - { - return false; - } - input.DropBits(8); - readAdler = (readAdler << 8) | dictByte; - neededBits -= 8; - } - return false; - } - - /// - /// Decodes the huffman encoded symbols in the input stream. - /// - /// - /// false if more input is needed, true if output window is - /// full or the current block ends. - /// - /// - /// if deflated stream is invalid. - /// - private bool DecodeHuffman() - { - int free = outputWindow.GetFreeSpace(); - while (free >= 258) - { - int symbol; - switch (mode) - { - case DECODE_HUFFMAN: - // This is the inner loop so it is optimized a bit - while (((symbol = litlenTree.GetSymbol(input)) & ~0xff) == 0) - { - outputWindow.Write(symbol); - if (--free < 258) - { - return true; - } - } - - if (symbol < 257) - { - if (symbol < 0) - { - return false; - } - else - { - // symbol == 256: end of block - distTree = null; - litlenTree = null; - mode = DECODE_BLOCKS; - return true; - } - } - - try - { - repLength = CPLENS[symbol - 257]; - neededBits = CPLEXT[symbol - 257]; - } - catch (Exception) - { - throw new SharpZipBaseException("Illegal rep length code"); - } - goto case DECODE_HUFFMAN_LENBITS; // fall through - - case DECODE_HUFFMAN_LENBITS: - if (neededBits > 0) - { - mode = DECODE_HUFFMAN_LENBITS; - int i = input.PeekBits(neededBits); - if (i < 0) - { - return false; - } - input.DropBits(neededBits); - repLength += i; - } - mode = DECODE_HUFFMAN_DIST; - goto case DECODE_HUFFMAN_DIST; // fall through - - case DECODE_HUFFMAN_DIST: - symbol = distTree.GetSymbol(input); - if (symbol < 0) - { - return false; - } - - try - { - repDist = CPDIST[symbol]; - neededBits = CPDEXT[symbol]; - } - catch (Exception) - { - throw new SharpZipBaseException("Illegal rep dist code"); - } - - goto case DECODE_HUFFMAN_DISTBITS; // fall through - - case DECODE_HUFFMAN_DISTBITS: - if (neededBits > 0) - { - mode = DECODE_HUFFMAN_DISTBITS; - int i = input.PeekBits(neededBits); - if (i < 0) - { - return false; - } - input.DropBits(neededBits); - repDist += i; - } - - outputWindow.Repeat(repLength, repDist); - free -= repLength; - mode = DECODE_HUFFMAN; - break; - - default: - throw new SharpZipBaseException("Inflater unknown mode"); - } - } - return true; - } - - /// - /// Decodes the adler checksum after the deflate stream. - /// - /// - /// false if more input is needed. - /// - /// - /// If checksum doesn't match. - /// - private bool DecodeChksum() - { - while (neededBits > 0) - { - int chkByte = input.PeekBits(8); - if (chkByte < 0) - { - return false; - } - input.DropBits(8); - readAdler = (readAdler << 8) | chkByte; - neededBits -= 8; - } - - if ((int)adler?.Value != readAdler) - { - throw new SharpZipBaseException("Adler chksum doesn't match: " + (int)adler?.Value + " vs. " + readAdler); - } - - mode = FINISHED; - return false; - } - - /// - /// Decodes the deflated stream. - /// - /// - /// false if more input is needed, or if finished. - /// - /// - /// if deflated stream is invalid. - /// - private bool Decode() - { - switch (mode) - { - case DECODE_HEADER: - return DecodeHeader(); - - case DECODE_DICT: - return DecodeDict(); - - case DECODE_CHKSUM: - return DecodeChksum(); - - case DECODE_BLOCKS: - if (isLastBlock) - { - if (noHeader) - { - mode = FINISHED; - return false; - } - else - { - input.SkipToByteBoundary(); - neededBits = 32; - mode = DECODE_CHKSUM; - return true; - } - } - - int type = input.PeekBits(3); - if (type < 0) - { - return false; - } - input.DropBits(3); - - isLastBlock |= (type & 1) != 0; - switch (type >> 1) - { - case DeflaterConstants.STORED_BLOCK: - input.SkipToByteBoundary(); - mode = DECODE_STORED_LEN1; - break; - - case DeflaterConstants.STATIC_TREES: - litlenTree = InflaterHuffmanTree.defLitLenTree; - distTree = InflaterHuffmanTree.defDistTree; - mode = DECODE_HUFFMAN; - break; - - case DeflaterConstants.DYN_TREES: - dynHeader = new InflaterDynHeader(input); - mode = DECODE_DYN_HEADER; - break; - - default: - throw new SharpZipBaseException("Unknown block type " + type); - } - return true; - - case DECODE_STORED_LEN1: - { - if ((uncomprLen = input.PeekBits(16)) < 0) - { - return false; - } - input.DropBits(16); - mode = DECODE_STORED_LEN2; - } - goto case DECODE_STORED_LEN2; // fall through - - case DECODE_STORED_LEN2: - { - int nlen = input.PeekBits(16); - if (nlen < 0) - { - return false; - } - input.DropBits(16); - if (nlen != (uncomprLen ^ 0xffff)) - { - throw new SharpZipBaseException("broken uncompressed block"); - } - mode = DECODE_STORED; - } - goto case DECODE_STORED; // fall through - - case DECODE_STORED: - { - int more = outputWindow.CopyStored(input, uncomprLen); - uncomprLen -= more; - if (uncomprLen == 0) - { - mode = DECODE_BLOCKS; - return true; - } - return !input.IsNeedingInput; - } - - case DECODE_DYN_HEADER: - if (!dynHeader.AttemptRead()) - { - return false; - } - - litlenTree = dynHeader.LiteralLengthTree; - distTree = dynHeader.DistanceTree; - mode = DECODE_HUFFMAN; - goto case DECODE_HUFFMAN; // fall through - - case DECODE_HUFFMAN: - case DECODE_HUFFMAN_LENBITS: - case DECODE_HUFFMAN_DIST: - case DECODE_HUFFMAN_DISTBITS: - return DecodeHuffman(); - - case FINISHED: - return false; - - default: - throw new SharpZipBaseException("Inflater.Decode unknown mode"); - } - } - - /// - /// Sets the preset dictionary. This should only be called, if - /// needsDictionary() returns true and it should set the same - /// dictionary, that was used for deflating. The getAdler() - /// function returns the checksum of the dictionary needed. - /// - /// - /// The dictionary. - /// - public void SetDictionary(byte[] buffer) - { - SetDictionary(buffer, 0, buffer.Length); - } - - /// - /// Sets the preset dictionary. This should only be called, if - /// needsDictionary() returns true and it should set the same - /// dictionary, that was used for deflating. The getAdler() - /// function returns the checksum of the dictionary needed. - /// - /// - /// The dictionary. - /// - /// - /// The index into buffer where the dictionary starts. - /// - /// - /// The number of bytes in the dictionary. - /// - /// - /// No dictionary is needed. - /// - /// - /// The adler checksum for the buffer is invalid - /// - public void SetDictionary(byte[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - if (!IsNeedingDictionary) - { - throw new InvalidOperationException("Dictionary is not needed"); - } - - adler?.Update(new ArraySegment(buffer, index, count)); - - if (adler != null && (int)adler.Value != readAdler) - { - throw new SharpZipBaseException("Wrong adler checksum"); - } - adler?.Reset(); - outputWindow.CopyDict(buffer, index, count); - mode = DECODE_BLOCKS; - } - - /// - /// Sets the input. This should only be called, if needsInput() - /// returns true. - /// - /// - /// the input. - /// - public void SetInput(byte[] buffer) - { - SetInput(buffer, 0, buffer.Length); - } - - /// - /// Sets the input. This should only be called, if needsInput() - /// returns true. - /// - /// - /// The source of input data - /// - /// - /// The index into buffer where the input starts. - /// - /// - /// The number of bytes of input to use. - /// - /// - /// No input is needed. - /// - /// - /// The index and/or count are wrong. - /// - public void SetInput(byte[] buffer, int index, int count) - { - input.SetInput(buffer, index, count); - totalIn += (long)count; - } - - /// - /// Inflates the compressed stream to the output buffer. If this - /// returns 0, you should check, whether IsNeedingDictionary(), - /// IsNeedingInput() or IsFinished() returns true, to determine why no - /// further output is produced. - /// - /// - /// the output buffer. - /// - /// - /// The number of bytes written to the buffer, 0 if no further - /// output can be produced. - /// - /// - /// if buffer has length 0. - /// - /// - /// if deflated stream is invalid. - /// - public int Inflate(byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - return Inflate(buffer, 0, buffer.Length); - } - - /// - /// Inflates the compressed stream to the output buffer. If this - /// returns 0, you should check, whether needsDictionary(), - /// needsInput() or finished() returns true, to determine why no - /// further output is produced. - /// - /// - /// the output buffer. - /// - /// - /// the offset in buffer where storing starts. - /// - /// - /// the maximum number of bytes to output. - /// - /// - /// the number of bytes written to the buffer, 0 if no further output can be produced. - /// - /// - /// if count is less than 0. - /// - /// - /// if the index and / or count are wrong. - /// - /// - /// if deflated stream is invalid. - /// - public int Inflate(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative"); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative"); - } - - if (offset + count > buffer.Length) - { - throw new ArgumentException("count exceeds buffer bounds"); - } - - // Special case: count may be zero - if (count == 0) - { - if (!IsFinished) - { // -jr- 08-Nov-2003 INFLATE_BUG fix.. - Decode(); - } - return 0; - } - - int bytesCopied = 0; - - do - { - if (mode != DECODE_CHKSUM) - { - /* Don't give away any output, if we are waiting for the - * checksum in the input stream. - * - * With this trick we have always: - * IsNeedingInput() and not IsFinished() - * implies more output can be produced. - */ - int more = outputWindow.CopyOutput(buffer, offset, count); - if (more > 0) - { - adler?.Update(new ArraySegment(buffer, offset, more)); - offset += more; - bytesCopied += more; - totalOut += (long)more; - count -= more; - if (count == 0) - { - return bytesCopied; - } - } - } - } while (Decode() || ((outputWindow.GetAvailable() > 0) && (mode != DECODE_CHKSUM))); - return bytesCopied; - } - - /// - /// Returns true, if the input buffer is empty. - /// You should then call setInput(). - /// NOTE: This method also returns true when the stream is finished. - /// - public bool IsNeedingInput - { - get - { - return input.IsNeedingInput; - } - } - - /// - /// Returns true, if a preset dictionary is needed to inflate the input. - /// - public bool IsNeedingDictionary - { - get - { - return mode == DECODE_DICT && neededBits == 0; - } - } - - /// - /// Returns true, if the inflater has finished. This means, that no - /// input is needed and no output can be produced. - /// - public bool IsFinished - { - get - { - return mode == FINISHED && outputWindow.GetAvailable() == 0; - } - } - - /// - /// Gets the adler checksum. This is either the checksum of all - /// uncompressed bytes returned by inflate(), or if needsDictionary() - /// returns true (and thus no output was yet produced) this is the - /// adler checksum of the expected dictionary. - /// - /// - /// the adler checksum. - /// - public int Adler - { - get - { - if (IsNeedingDictionary) - { - return readAdler; - } - else if (adler != null) - { - return (int)adler.Value; - } - else - { - return 0; - } - } - } - - /// - /// Gets the total number of output bytes returned by Inflate(). - /// - /// - /// the total number of output bytes. - /// - public long TotalOut - { - get - { - return totalOut; - } - } - - /// - /// Gets the total number of processed compressed input bytes. - /// - /// - /// The total number of bytes of processed input bytes. - /// - public long TotalIn - { - get - { - return totalIn - (long)RemainingInput; - } - } - - /// - /// Gets the number of unprocessed input bytes. Useful, if the end of the - /// stream is reached and you want to further process the bytes after - /// the deflate stream. - /// - /// - /// The number of bytes of the input which have not been processed. - /// - public int RemainingInput - { - // TODO: This should be a long? - get - { - return input.AvailableBytes; - } - } - } + #region Constants/Readonly + + /// + /// Copy lengths for literal codes 257..285 + /// + private static readonly int[] CPLENS = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + + /// + /// Extra bits for literal codes 257..285 + /// + private static readonly int[] CPLEXT = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + + /// + /// Copy offsets for distance codes 0..29 + /// + private static readonly int[] CPDIST = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + + /// + /// Extra bits for distance codes + /// + private static readonly int[] CPDEXT = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + }; + + /// + /// These are the possible states for an inflater + /// + private const int DECODE_HEADER = 0; + + private const int DECODE_DICT = 1; + private const int DECODE_BLOCKS = 2; + private const int DECODE_STORED_LEN1 = 3; + private const int DECODE_STORED_LEN2 = 4; + private const int DECODE_STORED = 5; + private const int DECODE_DYN_HEADER = 6; + private const int DECODE_HUFFMAN = 7; + private const int DECODE_HUFFMAN_LENBITS = 8; + private const int DECODE_HUFFMAN_DIST = 9; + private const int DECODE_HUFFMAN_DISTBITS = 10; + private const int DECODE_CHKSUM = 11; + private const int FINISHED = 12; + + #endregion Constants/Readonly + + #region Instance Fields + + /// + /// This variable contains the current state. + /// + private int mode; + + /// + /// The adler checksum of the dictionary or of the decompressed + /// stream, as it is written in the header resp. footer of the + /// compressed stream. + /// Only valid if mode is DECODE_DICT or DECODE_CHKSUM. + /// + private int readAdler; + + /// + /// The number of bits needed to complete the current state. This + /// is valid, if mode is DECODE_DICT, DECODE_CHKSUM, + /// DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. + /// + private int neededBits; + + private int repLength; + private int repDist; + private int uncomprLen; + + /// + /// True, if the last block flag was set in the last block of the + /// inflated stream. This means that the stream ends after the + /// current block. + /// + private bool isLastBlock; + + /// + /// The total number of inflated bytes. + /// + private long totalOut; + + /// + /// The total number of bytes set with setInput(). This is not the + /// value returned by the TotalIn property, since this also includes the + /// unprocessed input. + /// + private long totalIn; + + /// + /// This variable stores the noHeader flag that was given to the constructor. + /// True means, that the inflated stream doesn't contain a Zlib header or + /// footer. + /// + private readonly bool noHeader; + + private readonly StreamManipulator input; + private readonly OutputWindow outputWindow; + private InflaterDynHeader dynHeader; + private InflaterHuffmanTree litlenTree, distTree; + private readonly Adler32 adler; + + #endregion Instance Fields + + #region Constructors + + /// + /// Creates a new inflater or RFC1951 decompressor + /// RFC1950/Zlib headers and footers will be expected in the input data + /// + public Inflater() : this(false) + { + } + + /// + /// Creates a new inflater. + /// + /// + /// True if no RFC1950/Zlib header and footer fields are expected in the input data + /// + /// This is used for GZIPed/Zipped input. + /// + /// For compatibility with + /// Sun JDK you should provide one byte of input more than needed in + /// this case. + /// + public Inflater(bool noHeader) + { + this.noHeader = noHeader; + if (!noHeader) + this.adler = new Adler32(); + input = new StreamManipulator(); + outputWindow = new OutputWindow(); + mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; + } + + #endregion Constructors + + /// + /// Resets the inflater so that a new stream can be decompressed. All + /// pending input and output will be discarded. + /// + public void Reset() + { + mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER; + totalIn = 0; + totalOut = 0; + input.Reset(); + outputWindow.Reset(); + dynHeader = null; + litlenTree = null; + distTree = null; + isLastBlock = false; + adler?.Reset(); + } + + /// + /// Decodes a zlib/RFC1950 header. + /// + /// + /// False if more input is needed. + /// + /// + /// The header is invalid. + /// + private bool DecodeHeader() + { + var header = input.PeekBits(16); + if (header < 0) + { + return false; + } + input.DropBits(16); + + // The header is written in "wrong" byte order + header = ((header << 8) | (header >> 8)) & 0xffff; + if (header % 31 != 0) + { + throw new SharpZipBaseException("Header checksum illegal"); + } + + if ((header & 0x0f00) != (Deflater.DEFLATED << 8)) + { + throw new SharpZipBaseException("Compression Method unknown"); + } + + /* Maximum size of the backwards window in bits. + * We currently ignore this, but we could use it to make the + * inflater window more space efficient. On the other hand the + * full window (15 bits) is needed most times, anyway. + int max_wbits = ((header & 0x7000) >> 12) + 8; + */ + + if ((header & 0x0020) == 0) + { // Dictionary flag? + mode = DECODE_BLOCKS; + } + else + { + mode = DECODE_DICT; + neededBits = 32; + } + return true; + } + + /// + /// Decodes the dictionary checksum after the deflate header. + /// + /// + /// False if more input is needed. + /// + private bool DecodeDict() + { + while (neededBits > 0) + { + var dictByte = input.PeekBits(8); + if (dictByte < 0) + { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | dictByte; + neededBits -= 8; + } + return false; + } + + /// + /// Decodes the huffman encoded symbols in the input stream. + /// + /// + /// false if more input is needed, true if output window is + /// full or the current block ends. + /// + /// + /// if deflated stream is invalid. + /// + private bool DecodeHuffman() + { + var free = outputWindow.GetFreeSpace(); + while (free >= 258) + { + int symbol; + switch (mode) + { + case DECODE_HUFFMAN: + // This is the inner loop so it is optimized a bit + while (((symbol = litlenTree.GetSymbol(input)) & ~0xff) == 0) + { + outputWindow.Write(symbol); + if (--free < 258) + { + return true; + } + } + + if (symbol < 257) + { + if (symbol < 0) + { + return false; + } + else + { + // symbol == 256: end of block + distTree = null; + litlenTree = null; + mode = DECODE_BLOCKS; + return true; + } + } + + try + { + repLength = CPLENS[symbol - 257]; + neededBits = CPLEXT[symbol - 257]; + } + catch (Exception) + { + throw new SharpZipBaseException("Illegal rep length code"); + } + goto case DECODE_HUFFMAN_LENBITS; // fall through + + case DECODE_HUFFMAN_LENBITS: + if (neededBits > 0) + { + mode = DECODE_HUFFMAN_LENBITS; + var i = input.PeekBits(neededBits); + if (i < 0) + { + return false; + } + input.DropBits(neededBits); + repLength += i; + } + mode = DECODE_HUFFMAN_DIST; + goto case DECODE_HUFFMAN_DIST; // fall through + + case DECODE_HUFFMAN_DIST: + symbol = distTree.GetSymbol(input); + if (symbol < 0) + { + return false; + } + + try + { + repDist = CPDIST[symbol]; + neededBits = CPDEXT[symbol]; + } + catch (Exception) + { + throw new SharpZipBaseException("Illegal rep dist code"); + } + + goto case DECODE_HUFFMAN_DISTBITS; // fall through + + case DECODE_HUFFMAN_DISTBITS: + if (neededBits > 0) + { + mode = DECODE_HUFFMAN_DISTBITS; + var i = input.PeekBits(neededBits); + if (i < 0) + { + return false; + } + input.DropBits(neededBits); + repDist += i; + } + + outputWindow.Repeat(repLength, repDist); + free -= repLength; + mode = DECODE_HUFFMAN; + break; + + default: + throw new SharpZipBaseException("Inflater unknown mode"); + } + } + return true; + } + + /// + /// Decodes the adler checksum after the deflate stream. + /// + /// + /// false if more input is needed. + /// + /// + /// If checksum doesn't match. + /// + private bool DecodeChksum() + { + while (neededBits > 0) + { + var chkByte = input.PeekBits(8); + if (chkByte < 0) + { + return false; + } + input.DropBits(8); + readAdler = (readAdler << 8) | chkByte; + neededBits -= 8; + } + + if ((int)adler?.Value != readAdler) + { + throw new SharpZipBaseException("Adler chksum doesn't match: " + (int)adler?.Value + " vs. " + readAdler); + } + + mode = FINISHED; + return false; + } + + /// + /// Decodes the deflated stream. + /// + /// + /// false if more input is needed, or if finished. + /// + /// + /// if deflated stream is invalid. + /// + private bool Decode() + { + switch (mode) + { + case DECODE_HEADER: + return DecodeHeader(); + + case DECODE_DICT: + return DecodeDict(); + + case DECODE_CHKSUM: + return DecodeChksum(); + + case DECODE_BLOCKS: + if (isLastBlock) + { + if (noHeader) + { + mode = FINISHED; + return false; + } + else + { + input.SkipToByteBoundary(); + neededBits = 32; + mode = DECODE_CHKSUM; + return true; + } + } + + var type = input.PeekBits(3); + if (type < 0) + { + return false; + } + input.DropBits(3); + + isLastBlock |= (type & 1) != 0; + switch (type >> 1) + { + case DeflaterConstants.STORED_BLOCK: + input.SkipToByteBoundary(); + mode = DECODE_STORED_LEN1; + break; + + case DeflaterConstants.STATIC_TREES: + litlenTree = InflaterHuffmanTree.defLitLenTree; + distTree = InflaterHuffmanTree.defDistTree; + mode = DECODE_HUFFMAN; + break; + + case DeflaterConstants.DYN_TREES: + dynHeader = new InflaterDynHeader(input); + mode = DECODE_DYN_HEADER; + break; + + default: + throw new SharpZipBaseException("Unknown block type " + type); + } + return true; + + case DECODE_STORED_LEN1: + { + if ((uncomprLen = input.PeekBits(16)) < 0) + { + return false; + } + input.DropBits(16); + mode = DECODE_STORED_LEN2; + } + goto case DECODE_STORED_LEN2; // fall through + + case DECODE_STORED_LEN2: + { + var nlen = input.PeekBits(16); + if (nlen < 0) + { + return false; + } + input.DropBits(16); + if (nlen != (uncomprLen ^ 0xffff)) + { + throw new SharpZipBaseException("broken uncompressed block"); + } + mode = DECODE_STORED; + } + goto case DECODE_STORED; // fall through + + case DECODE_STORED: + { + var more = outputWindow.CopyStored(input, uncomprLen); + uncomprLen -= more; + if (uncomprLen == 0) + { + mode = DECODE_BLOCKS; + return true; + } + return !input.IsNeedingInput; + } + + case DECODE_DYN_HEADER: + if (!dynHeader.AttemptRead()) + { + return false; + } + + litlenTree = dynHeader.LiteralLengthTree; + distTree = dynHeader.DistanceTree; + mode = DECODE_HUFFMAN; + goto case DECODE_HUFFMAN; // fall through + + case DECODE_HUFFMAN: + case DECODE_HUFFMAN_LENBITS: + case DECODE_HUFFMAN_DIST: + case DECODE_HUFFMAN_DISTBITS: + return DecodeHuffman(); + + case FINISHED: + return false; + + default: + throw new SharpZipBaseException("Inflater.Decode unknown mode"); + } + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// The dictionary. + /// + public void SetDictionary(byte[] buffer) + { + SetDictionary(buffer, 0, buffer.Length); + } + + /// + /// Sets the preset dictionary. This should only be called, if + /// needsDictionary() returns true and it should set the same + /// dictionary, that was used for deflating. The getAdler() + /// function returns the checksum of the dictionary needed. + /// + /// + /// The dictionary. + /// + /// + /// The index into buffer where the dictionary starts. + /// + /// + /// The number of bytes in the dictionary. + /// + /// + /// No dictionary is needed. + /// + /// + /// The adler checksum for the buffer is invalid + /// + public void SetDictionary(byte[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (!IsNeedingDictionary) + { + throw new InvalidOperationException("Dictionary is not needed"); + } + + adler?.Update(new ArraySegment(buffer, index, count)); + + if (adler != null && (int)adler.Value != readAdler) + { + throw new SharpZipBaseException("Wrong adler checksum"); + } + adler?.Reset(); + outputWindow.CopyDict(buffer, index, count); + mode = DECODE_BLOCKS; + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// the input. + /// + public void SetInput(byte[] buffer) + { + SetInput(buffer, 0, buffer.Length); + } + + /// + /// Sets the input. This should only be called, if needsInput() + /// returns true. + /// + /// + /// The source of input data + /// + /// + /// The index into buffer where the input starts. + /// + /// + /// The number of bytes of input to use. + /// + /// + /// No input is needed. + /// + /// + /// The index and/or count are wrong. + /// + public void SetInput(byte[] buffer, int index, int count) + { + input.SetInput(buffer, index, count); + totalIn += count; + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether IsNeedingDictionary(), + /// IsNeedingInput() or IsFinished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// The number of bytes written to the buffer, 0 if no further + /// output can be produced. + /// + /// + /// if buffer has length 0. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buffer) + { + return buffer == null ? throw new ArgumentNullException(nameof(buffer)) : Inflate(buffer, 0, buffer.Length); + } + + /// + /// Inflates the compressed stream to the output buffer. If this + /// returns 0, you should check, whether needsDictionary(), + /// needsInput() or finished() returns true, to determine why no + /// further output is produced. + /// + /// + /// the output buffer. + /// + /// + /// the offset in buffer where storing starts. + /// + /// + /// the maximum number of bytes to output. + /// + /// + /// the number of bytes written to the buffer, 0 if no further output can be produced. + /// + /// + /// if count is less than 0. + /// + /// + /// if the index and / or count are wrong. + /// + /// + /// if deflated stream is invalid. + /// + public int Inflate(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative"); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative"); + } + + if (offset + count > buffer.Length) + { + throw new ArgumentException("count exceeds buffer bounds"); + } + + // Special case: count may be zero + if (count == 0) + { + if (!IsFinished) + { // -jr- 08-Nov-2003 INFLATE_BUG fix.. + Decode(); + } + return 0; + } + + var bytesCopied = 0; + + do + { + if (mode != DECODE_CHKSUM) + { + /* Don't give away any output, if we are waiting for the + * checksum in the input stream. + * + * With this trick we have always: + * IsNeedingInput() and not IsFinished() + * implies more output can be produced. + */ + var more = outputWindow.CopyOutput(buffer, offset, count); + if (more > 0) + { + adler?.Update(new ArraySegment(buffer, offset, more)); + offset += more; + bytesCopied += more; + totalOut += more; + count -= more; + if (count == 0) + { + return bytesCopied; + } + } + } + } while (Decode() || ((outputWindow.GetAvailable() > 0) && (mode != DECODE_CHKSUM))); + return bytesCopied; + } + + /// + /// Returns true, if the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method also returns true when the stream is finished. + /// + public bool IsNeedingInput + { + get + { + return input.IsNeedingInput; + } + } + + /// + /// Returns true, if a preset dictionary is needed to inflate the input. + /// + public bool IsNeedingDictionary + { + get + { + return mode == DECODE_DICT && neededBits == 0; + } + } + + /// + /// Returns true, if the inflater has finished. This means, that no + /// input is needed and no output can be produced. + /// + public bool IsFinished + { + get + { + return mode == FINISHED && outputWindow.GetAvailable() == 0; + } + } + + /// + /// Gets the adler checksum. This is either the checksum of all + /// uncompressed bytes returned by inflate(), or if needsDictionary() + /// returns true (and thus no output was yet produced) this is the + /// adler checksum of the expected dictionary. + /// + /// + /// the adler checksum. + /// + public int Adler + { + get + { + if (IsNeedingDictionary) + { + return readAdler; + } + else + { + return adler != null ? (int)adler.Value : 0; + } + } + } + + /// + /// Gets the total number of output bytes returned by Inflate(). + /// + /// + /// the total number of output bytes. + /// + public long TotalOut + { + get + { + return totalOut; + } + } + + /// + /// Gets the total number of processed compressed input bytes. + /// + /// + /// The total number of bytes of processed input bytes. + /// + public long TotalIn + { + get + { + return totalIn - RemainingInput; + } + } + + /// + /// Gets the number of unprocessed input bytes. Useful, if the end of the + /// stream is reached and you want to further process the bytes after + /// the deflate stream. + /// + /// + /// The number of bytes of the input which have not been processed. + /// + public int RemainingInput + { + // TODO: This should be a long? + get + { + return input.AvailableBytes; + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs index fb2693145..63b1c557f 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs @@ -1,152 +1,160 @@ using MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; -using System; using System.Collections.Generic; -using System.Linq; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +internal class InflaterDynHeader { - internal class InflaterDynHeader - { - #region Constants - - // maximum number of literal/length codes - private const int LITLEN_MAX = 286; - - // maximum number of distance codes - private const int DIST_MAX = 30; - - // maximum data code lengths to read - private const int CODELEN_MAX = LITLEN_MAX + DIST_MAX; - - // maximum meta code length codes to read - private const int META_MAX = 19; - - private static readonly int[] MetaCodeLengthIndex = - { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - - #endregion Constants - - /// - /// Continue decoding header from until more bits are needed or decoding has been completed - /// - /// Returns whether decoding could be completed - public bool AttemptRead() - => !state.MoveNext() || state.Current; - - public InflaterDynHeader(StreamManipulator input) - { - this.input = input; - stateMachine = CreateStateMachine(); - state = stateMachine.GetEnumerator(); - } - - private IEnumerable CreateStateMachine() - { - // Read initial code length counts from header - while (!input.TryGetBits(5, ref litLenCodeCount, 257)) yield return false; - while (!input.TryGetBits(5, ref distanceCodeCount, 1)) yield return false; - while (!input.TryGetBits(4, ref metaCodeCount, 4)) yield return false; - var dataCodeCount = litLenCodeCount + distanceCodeCount; - - if (litLenCodeCount > LITLEN_MAX) throw new ValueOutOfRangeException(nameof(litLenCodeCount)); - if (distanceCodeCount > DIST_MAX) throw new ValueOutOfRangeException(nameof(distanceCodeCount)); - if (metaCodeCount > META_MAX) throw new ValueOutOfRangeException(nameof(metaCodeCount)); - - // Load code lengths for the meta tree from the header bits - for (int i = 0; i < metaCodeCount; i++) - { - while (!input.TryGetBits(3, ref codeLengths, MetaCodeLengthIndex[i])) yield return false; - } - - var metaCodeTree = new InflaterHuffmanTree(codeLengths); - - // Decompress the meta tree symbols into the data table code lengths - int index = 0; - while (index < dataCodeCount) - { - byte codeLength; - int symbol; - - while ((symbol = metaCodeTree.GetSymbol(input)) < 0) yield return false; - - if (symbol < 16) - { - // append literal code length - codeLengths[index++] = (byte)symbol; - } - else - { - int repeatCount = 0; - - if (symbol == 16) // Repeat last code length 3..6 times - { - if (index == 0) - throw new StreamDecodingException("Cannot repeat previous code length when no other code length has been read"); - - codeLength = codeLengths[index - 1]; - - // 2 bits + 3, [3..6] - while (!input.TryGetBits(2, ref repeatCount, 3)) yield return false; - } - else if (symbol == 17) // Repeat zero 3..10 times - { - codeLength = 0; - - // 3 bits + 3, [3..10] - while (!input.TryGetBits(3, ref repeatCount, 3)) yield return false; - } - else // (symbol == 18), Repeat zero 11..138 times - { - codeLength = 0; - - // 7 bits + 11, [11..138] - while (!input.TryGetBits(7, ref repeatCount, 11)) yield return false; - } - - if (index + repeatCount > dataCodeCount) - throw new StreamDecodingException("Cannot repeat code lengths past total number of data code lengths"); - - while (repeatCount-- > 0) - codeLengths[index++] = codeLength; - } - } - - if (codeLengths[256] == 0) - throw new StreamDecodingException("Inflater dynamic header end-of-block code missing"); - - litLenTree = new InflaterHuffmanTree(new LemonArraySegment(codeLengths, 0, litLenCodeCount)); - distTree = new InflaterHuffmanTree(new LemonArraySegment(codeLengths, litLenCodeCount, distanceCodeCount)); - - yield return true; - } - - /// - /// Get literal/length huffman tree, must not be used before has returned true - /// - /// If hader has not been successfully read by the state machine - public InflaterHuffmanTree LiteralLengthTree - => litLenTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read"); - - /// - /// Get distance huffman tree, must not be used before has returned true - /// - /// If hader has not been successfully read by the state machine - public InflaterHuffmanTree DistanceTree - => distTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read"); - - #region Instance Fields - - private readonly StreamManipulator input; - private readonly IEnumerator state; - private readonly IEnumerable stateMachine; - - private byte[] codeLengths = new byte[CODELEN_MAX]; - - private InflaterHuffmanTree litLenTree; - private InflaterHuffmanTree distTree; - - private int litLenCodeCount, distanceCodeCount, metaCodeCount; - - #endregion Instance Fields - } + #region Constants + + // maximum number of literal/length codes + private const int LITLEN_MAX = 286; + + // maximum number of distance codes + private const int DIST_MAX = 30; + + // maximum data code lengths to read + private const int CODELEN_MAX = LITLEN_MAX + DIST_MAX; + + // maximum meta code length codes to read + private const int META_MAX = 19; + + private static readonly int[] MetaCodeLengthIndex = + { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + #endregion Constants + + /// + /// Continue decoding header from until more bits are needed or decoding has been completed + /// + /// Returns whether decoding could be completed + public bool AttemptRead() + => !state.MoveNext() || state.Current; + + public InflaterDynHeader(StreamManipulator input) + { + this.input = input; + stateMachine = CreateStateMachine(); + state = stateMachine.GetEnumerator(); + } + + private IEnumerable CreateStateMachine() + { + // Read initial code length counts from header + while (!input.TryGetBits(5, ref litLenCodeCount, 257)) + yield return false; + while (!input.TryGetBits(5, ref distanceCodeCount, 1)) + yield return false; + while (!input.TryGetBits(4, ref metaCodeCount, 4)) + yield return false; + var dataCodeCount = litLenCodeCount + distanceCodeCount; + + if (litLenCodeCount > LITLEN_MAX) + throw new ValueOutOfRangeException(nameof(litLenCodeCount)); + if (distanceCodeCount > DIST_MAX) + throw new ValueOutOfRangeException(nameof(distanceCodeCount)); + if (metaCodeCount > META_MAX) + throw new ValueOutOfRangeException(nameof(metaCodeCount)); + + // Load code lengths for the meta tree from the header bits + for (var i = 0; i < metaCodeCount; i++) + { + while (!input.TryGetBits(3, ref codeLengths, MetaCodeLengthIndex[i])) + yield return false; + } + + var metaCodeTree = new InflaterHuffmanTree(codeLengths); + + // Decompress the meta tree symbols into the data table code lengths + var index = 0; + while (index < dataCodeCount) + { + byte codeLength; + int symbol; + + while ((symbol = metaCodeTree.GetSymbol(input)) < 0) + yield return false; + + if (symbol < 16) + { + // append literal code length + codeLengths[index++] = (byte)symbol; + } + else + { + var repeatCount = 0; + + if (symbol == 16) // Repeat last code length 3..6 times + { + if (index == 0) + throw new StreamDecodingException("Cannot repeat previous code length when no other code length has been read"); + + codeLength = codeLengths[index - 1]; + + // 2 bits + 3, [3..6] + while (!input.TryGetBits(2, ref repeatCount, 3)) + yield return false; + } + else if (symbol == 17) // Repeat zero 3..10 times + { + codeLength = 0; + + // 3 bits + 3, [3..10] + while (!input.TryGetBits(3, ref repeatCount, 3)) + yield return false; + } + else // (symbol == 18), Repeat zero 11..138 times + { + codeLength = 0; + + // 7 bits + 11, [11..138] + while (!input.TryGetBits(7, ref repeatCount, 11)) + yield return false; + } + + if (index + repeatCount > dataCodeCount) + throw new StreamDecodingException("Cannot repeat code lengths past total number of data code lengths"); + + while (repeatCount-- > 0) + codeLengths[index++] = codeLength; + } + } + + if (codeLengths[256] == 0) + throw new StreamDecodingException("Inflater dynamic header end-of-block code missing"); + + litLenTree = new InflaterHuffmanTree(new LemonArraySegment(codeLengths, 0, litLenCodeCount)); + distTree = new InflaterHuffmanTree(new LemonArraySegment(codeLengths, litLenCodeCount, distanceCodeCount)); + + yield return true; + } + + /// + /// Get literal/length huffman tree, must not be used before has returned true + /// + /// If hader has not been successfully read by the state machine + public InflaterHuffmanTree LiteralLengthTree + => litLenTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read"); + + /// + /// Get distance huffman tree, must not be used before has returned true + /// + /// If hader has not been successfully read by the state machine + public InflaterHuffmanTree DistanceTree + => distTree ?? throw new StreamDecodingException("Header properties were accessed before header had been successfully read"); + + #region Instance Fields + + private readonly StreamManipulator input; + private readonly IEnumerator state; + private readonly IEnumerable stateMachine; + + private byte[] codeLengths = new byte[CODELEN_MAX]; + + private InflaterHuffmanTree litLenTree; + private InflaterHuffmanTree distTree; + + private int litLenCodeCount, distanceCodeCount, metaCodeCount; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs index b55e52733..e277b9d29 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs @@ -2,236 +2,236 @@ using System; using System.Collections.Generic; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// Huffman tree used for inflation +/// +public class InflaterHuffmanTree { - /// - /// Huffman tree used for inflation - /// - public class InflaterHuffmanTree - { - #region Constants - - private const int MAX_BITLEN = 15; - - #endregion Constants - - #region Instance Fields - - private short[] tree; - - #endregion Instance Fields - - /// - /// Literal length tree - /// - public static InflaterHuffmanTree defLitLenTree; - - /// - /// Distance tree - /// - public static InflaterHuffmanTree defDistTree; - - static InflaterHuffmanTree() - { - try - { - byte[] codeLengths = new byte[288]; - int i = 0; - while (i < 144) - { - codeLengths[i++] = 8; - } - while (i < 256) - { - codeLengths[i++] = 9; - } - while (i < 280) - { - codeLengths[i++] = 7; - } - while (i < 288) - { - codeLengths[i++] = 8; - } - defLitLenTree = new InflaterHuffmanTree(codeLengths); - - codeLengths = new byte[32]; - i = 0; - while (i < 32) - { - codeLengths[i++] = 5; - } - defDistTree = new InflaterHuffmanTree(codeLengths); - } - catch (Exception) - { - throw new SharpZipBaseException("InflaterHuffmanTree: static tree length illegal"); - } - } - - #region Constructors - - /// - /// Constructs a Huffman tree from the array of code lengths. - /// - /// - /// the array of code lengths - /// - public InflaterHuffmanTree(IList codeLengths) - { - BuildTree(codeLengths); - } - - #endregion Constructors - - private void BuildTree(IList codeLengths) - { - int[] blCount = new int[MAX_BITLEN + 1]; - int[] nextCode = new int[MAX_BITLEN + 1]; - - for (int i = 0; i < codeLengths.Count; i++) - { - int bits = codeLengths[i]; - if (bits > 0) - { - blCount[bits]++; - } - } - - int code = 0; - int treeSize = 512; - for (int bits = 1; bits <= MAX_BITLEN; bits++) - { - nextCode[bits] = code; - code += blCount[bits] << (16 - bits); - if (bits >= 10) - { - /* We need an extra table for bit lengths >= 10. */ - int start = nextCode[bits] & 0x1ff80; - int end = code & 0x1ff80; - treeSize += (end - start) >> (16 - bits); - } - } - - /* -jr comment this out! doesnt work for dynamic trees and pkzip 2.04g - if (code != 65536) - { - throw new SharpZipBaseException("Code lengths don't add up properly."); - } - */ - /* Now create and fill the extra tables from longest to shortest - * bit len. This way the sub trees will be aligned. - */ - tree = new short[treeSize]; - int treePtr = 512; - for (int bits = MAX_BITLEN; bits >= 10; bits--) - { - int end = code & 0x1ff80; - code -= blCount[bits] << (16 - bits); - int start = code & 0x1ff80; - for (int i = start; i < end; i += 1 << 7) - { - tree[DeflaterHuffman.BitReverse(i)] = (short)((-treePtr << 4) | bits); - treePtr += 1 << (bits - 9); - } - } - - for (int i = 0; i < codeLengths.Count; i++) - { - int bits = codeLengths[i]; - if (bits == 0) - { - continue; - } - code = nextCode[bits]; - int revcode = DeflaterHuffman.BitReverse(code); - if (bits <= 9) - { - do - { - tree[revcode] = (short)((i << 4) | bits); - revcode += 1 << bits; - } while (revcode < 512); - } - else - { - int subTree = tree[revcode & 511]; - int treeLen = 1 << (subTree & 15); - subTree = -(subTree >> 4); - do - { - tree[subTree | (revcode >> 9)] = (short)((i << 4) | bits); - revcode += 1 << bits; - } while (revcode < treeLen); - } - nextCode[bits] = code + (1 << (16 - bits)); - } - } - - /// - /// Reads the next symbol from input. The symbol is encoded using the - /// huffman tree. - /// - /// - /// input the input source. - /// - /// - /// the next symbol, or -1 if not enough input is available. - /// - public int GetSymbol(StreamManipulator input) - { - int lookahead, symbol; - if ((lookahead = input.PeekBits(9)) >= 0) - { - symbol = tree[lookahead]; - int bitlen = symbol & 15; - - if (symbol >= 0) - { - if(bitlen == 0){ - throw new SharpZipBaseException("Encountered invalid codelength 0"); - } - input.DropBits(bitlen); - return symbol >> 4; - } - int subtree = -(symbol >> 4); - if ((lookahead = input.PeekBits(bitlen)) >= 0) - { - symbol = tree[subtree | (lookahead >> 9)]; - input.DropBits(symbol & 15); - return symbol >> 4; - } - else - { - int bits = input.AvailableBits; - lookahead = input.PeekBits(bits); - symbol = tree[subtree | (lookahead >> 9)]; - if ((symbol & 15) <= bits) - { - input.DropBits(symbol & 15); - return symbol >> 4; - } - else - { - return -1; - } - } - } - else // Less than 9 bits - { - int bits = input.AvailableBits; - lookahead = input.PeekBits(bits); - symbol = tree[lookahead]; - if (symbol >= 0 && (symbol & 15) <= bits) - { - input.DropBits(symbol & 15); - return symbol >> 4; - } - else - { - return -1; - } - } - } - } + #region Constants + + private const int MAX_BITLEN = 15; + + #endregion Constants + + #region Instance Fields + + private short[] tree; + + #endregion Instance Fields + + /// + /// Literal length tree + /// + public static InflaterHuffmanTree defLitLenTree; + + /// + /// Distance tree + /// + public static InflaterHuffmanTree defDistTree; + + static InflaterHuffmanTree() + { + try + { + var codeLengths = new byte[288]; + var i = 0; + while (i < 144) + { + codeLengths[i++] = 8; + } + while (i < 256) + { + codeLengths[i++] = 9; + } + while (i < 280) + { + codeLengths[i++] = 7; + } + while (i < 288) + { + codeLengths[i++] = 8; + } + defLitLenTree = new InflaterHuffmanTree(codeLengths); + + codeLengths = new byte[32]; + i = 0; + while (i < 32) + { + codeLengths[i++] = 5; + } + defDistTree = new InflaterHuffmanTree(codeLengths); + } + catch (Exception) + { + throw new SharpZipBaseException("InflaterHuffmanTree: static tree length illegal"); + } + } + + #region Constructors + + /// + /// Constructs a Huffman tree from the array of code lengths. + /// + /// + /// the array of code lengths + /// + public InflaterHuffmanTree(IList codeLengths) + { + BuildTree(codeLengths); + } + + #endregion Constructors + + private void BuildTree(IList codeLengths) + { + var blCount = new int[MAX_BITLEN + 1]; + var nextCode = new int[MAX_BITLEN + 1]; + + for (var i = 0; i < codeLengths.Count; i++) + { + int bits = codeLengths[i]; + if (bits > 0) + { + blCount[bits]++; + } + } + + var code = 0; + var treeSize = 512; + for (var bits = 1; bits <= MAX_BITLEN; bits++) + { + nextCode[bits] = code; + code += blCount[bits] << (16 - bits); + if (bits >= 10) + { + /* We need an extra table for bit lengths >= 10. */ + var start = nextCode[bits] & 0x1ff80; + var end = code & 0x1ff80; + treeSize += (end - start) >> (16 - bits); + } + } + + /* -jr comment this out! doesnt work for dynamic trees and pkzip 2.04g + if (code != 65536) + { + throw new SharpZipBaseException("Code lengths don't add up properly."); + } + */ + /* Now create and fill the extra tables from longest to shortest + * bit len. This way the sub trees will be aligned. + */ + tree = new short[treeSize]; + var treePtr = 512; + for (var bits = MAX_BITLEN; bits >= 10; bits--) + { + var end = code & 0x1ff80; + code -= blCount[bits] << (16 - bits); + var start = code & 0x1ff80; + for (var i = start; i < end; i += 1 << 7) + { + tree[DeflaterHuffman.BitReverse(i)] = (short)((-treePtr << 4) | bits); + treePtr += 1 << (bits - 9); + } + } + + for (var i = 0; i < codeLengths.Count; i++) + { + int bits = codeLengths[i]; + if (bits == 0) + { + continue; + } + code = nextCode[bits]; + int revcode = DeflaterHuffman.BitReverse(code); + if (bits <= 9) + { + do + { + tree[revcode] = (short)((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < 512); + } + else + { + int subTree = tree[revcode & 511]; + var treeLen = 1 << (subTree & 15); + subTree = -(subTree >> 4); + do + { + tree[subTree | (revcode >> 9)] = (short)((i << 4) | bits); + revcode += 1 << bits; + } while (revcode < treeLen); + } + nextCode[bits] = code + (1 << (16 - bits)); + } + } + + /// + /// Reads the next symbol from input. The symbol is encoded using the + /// huffman tree. + /// + /// + /// input the input source. + /// + /// + /// the next symbol, or -1 if not enough input is available. + /// + public int GetSymbol(StreamManipulator input) + { + int lookahead, symbol; + if ((lookahead = input.PeekBits(9)) >= 0) + { + symbol = tree[lookahead]; + var bitlen = symbol & 15; + + if (symbol >= 0) + { + if (bitlen == 0) + { + throw new SharpZipBaseException("Encountered invalid codelength 0"); + } + input.DropBits(bitlen); + return symbol >> 4; + } + var subtree = -(symbol >> 4); + if ((lookahead = input.PeekBits(bitlen)) >= 0) + { + symbol = tree[subtree | (lookahead >> 9)]; + input.DropBits(symbol & 15); + return symbol >> 4; + } + else + { + var bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[subtree | (lookahead >> 9)]; + if ((symbol & 15) <= bits) + { + input.DropBits(symbol & 15); + return symbol >> 4; + } + else + { + return -1; + } + } + } + else // Less than 9 bits + { + var bits = input.AvailableBits; + lookahead = input.PeekBits(bits); + symbol = tree[lookahead]; + if (symbol >= 0 && (symbol & 15) <= bits) + { + input.DropBits(symbol & 15); + return symbol >> 4; + } + else + { + return -1; + } + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs index a1fe45200..5264e5a22 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs @@ -1,172 +1,172 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; + +/// +/// This class is general purpose class for writing data to a buffer. +/// +/// It allows you to write bits as well as bytes +/// Based on DeflaterPending.java +/// +/// author of the original java version : Jochen Hoenicke +/// +public class PendingBuffer { - /// - /// This class is general purpose class for writing data to a buffer. - /// - /// It allows you to write bits as well as bytes - /// Based on DeflaterPending.java - /// - /// author of the original java version : Jochen Hoenicke - /// - public class PendingBuffer - { - #region Instance Fields + #region Instance Fields - /// - /// Internal work buffer - /// - private readonly byte[] buffer; + /// + /// Internal work buffer + /// + private readonly byte[] buffer; - private int start; - private int end; + private int start; + private int end; - private uint bits; - private int bitCount; + private uint bits; + private int bitCount; - #endregion Instance Fields + #endregion Instance Fields - #region Constructors + #region Constructors - /// - /// construct instance using default buffer size of 4096 - /// - public PendingBuffer() : this(4096) - { - } + /// + /// construct instance using default buffer size of 4096 + /// + public PendingBuffer() : this(4096) + { + } - /// - /// construct instance using specified buffer size - /// - /// - /// size to use for internal buffer - /// - public PendingBuffer(int bufferSize) - { - buffer = new byte[bufferSize]; - } + /// + /// construct instance using specified buffer size + /// + /// + /// size to use for internal buffer + /// + public PendingBuffer(int bufferSize) + { + buffer = new byte[bufferSize]; + } - #endregion Constructors + #endregion Constructors - /// - /// Clear internal state/buffers - /// - public void Reset() - { - start = end = bitCount = 0; - } + /// + /// Clear internal state/buffers + /// + public void Reset() + { + start = end = bitCount = 0; + } - /// - /// Write a byte to buffer - /// - /// - /// The value to write - /// - public void WriteByte(int value) - { + /// + /// Write a byte to buffer + /// + /// + /// The value to write + /// + public void WriteByte(int value) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (start != 0) ) { throw new SharpZipBaseException("Debug check: start != 0"); } #endif - buffer[end++] = unchecked((byte)value); - } + buffer[end++] = unchecked((byte)value); + } - /// - /// Write a short value to buffer LSB first - /// - /// - /// The value to write. - /// - public void WriteShort(int value) - { + /// + /// Write a short value to buffer LSB first + /// + /// + /// The value to write. + /// + public void WriteShort(int value) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (start != 0) ) { throw new SharpZipBaseException("Debug check: start != 0"); } #endif - buffer[end++] = unchecked((byte)value); - buffer[end++] = unchecked((byte)(value >> 8)); - } + buffer[end++] = unchecked((byte)value); + buffer[end++] = unchecked((byte)(value >> 8)); + } - /// - /// write an integer LSB first - /// - /// The value to write. - public void WriteInt(int value) - { + /// + /// write an integer LSB first + /// + /// The value to write. + public void WriteInt(int value) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (start != 0) ) { throw new SharpZipBaseException("Debug check: start != 0"); } #endif - buffer[end++] = unchecked((byte)value); - buffer[end++] = unchecked((byte)(value >> 8)); - buffer[end++] = unchecked((byte)(value >> 16)); - buffer[end++] = unchecked((byte)(value >> 24)); - } + buffer[end++] = unchecked((byte)value); + buffer[end++] = unchecked((byte)(value >> 8)); + buffer[end++] = unchecked((byte)(value >> 16)); + buffer[end++] = unchecked((byte)(value >> 24)); + } - /// - /// Write a block of data to buffer - /// - /// data to write - /// offset of first byte to write - /// number of bytes to write - public void WriteBlock(byte[] block, int offset, int length) - { + /// + /// Write a block of data to buffer + /// + /// data to write + /// offset of first byte to write + /// number of bytes to write + public void WriteBlock(byte[] block, int offset, int length) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (start != 0) ) { throw new SharpZipBaseException("Debug check: start != 0"); } #endif - System.Array.Copy(block, offset, buffer, end, length); - end += length; - } + System.Array.Copy(block, offset, buffer, end, length); + end += length; + } - /// - /// The number of bits written to the buffer - /// - public int BitCount - { - get - { - return bitCount; - } - } + /// + /// The number of bits written to the buffer + /// + public int BitCount + { + get + { + return bitCount; + } + } - /// - /// Align internal buffer on a byte boundary - /// - public void AlignToByte() - { + /// + /// Align internal buffer on a byte boundary + /// + public void AlignToByte() + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (start != 0) ) { throw new SharpZipBaseException("Debug check: start != 0"); } #endif - if (bitCount > 0) - { - buffer[end++] = unchecked((byte)bits); - if (bitCount > 8) - { - buffer[end++] = unchecked((byte)(bits >> 8)); - } - } - bits = 0; - bitCount = 0; - } + if (bitCount > 0) + { + buffer[end++] = unchecked((byte)bits); + if (bitCount > 8) + { + buffer[end++] = unchecked((byte)(bits >> 8)); + } + } + bits = 0; + bitCount = 0; + } - /// - /// Write bits to internal buffer - /// - /// source of bits - /// number of bits to write - public void WriteBits(int b, int count) - { + /// + /// Write bits to internal buffer + /// + /// source of bits + /// number of bits to write + public void WriteBits(int b, int count) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (start != 0) ) { @@ -177,92 +177,91 @@ public void WriteBits(int b, int count) // //Console.WriteLine("writeBits("+b+","+count+")"); // } #endif - bits |= (uint)(b << bitCount); - bitCount += count; - if (bitCount >= 16) - { - buffer[end++] = unchecked((byte)bits); - buffer[end++] = unchecked((byte)(bits >> 8)); - bits >>= 16; - bitCount -= 16; - } - } + bits |= (uint)(b << bitCount); + bitCount += count; + if (bitCount >= 16) + { + buffer[end++] = unchecked((byte)bits); + buffer[end++] = unchecked((byte)(bits >> 8)); + bits >>= 16; + bitCount -= 16; + } + } - /// - /// Write a short value to internal buffer most significant byte first - /// - /// value to write - public void WriteShortMSB(int s) - { + /// + /// Write a short value to internal buffer most significant byte first + /// + /// value to write + public void WriteShortMSB(int s) + { #if DebugDeflation if (DeflaterConstants.DEBUGGING && (start != 0) ) { throw new SharpZipBaseException("Debug check: start != 0"); } #endif - buffer[end++] = unchecked((byte)(s >> 8)); - buffer[end++] = unchecked((byte)s); - } + buffer[end++] = unchecked((byte)(s >> 8)); + buffer[end++] = unchecked((byte)s); + } - /// - /// Indicates if buffer has been flushed - /// - public bool IsFlushed - { - get - { - return end == 0; - } - } + /// + /// Indicates if buffer has been flushed + /// + public bool IsFlushed + { + get + { + return end == 0; + } + } - /// - /// Flushes the pending buffer into the given output array. If the - /// output array is to small, only a partial flush is done. - /// - /// The output array. - /// The offset into output array. - /// The maximum number of bytes to store. - /// The number of bytes flushed. - public int Flush(byte[] output, int offset, int length) - { - if (bitCount >= 8) - { - buffer[end++] = unchecked((byte)bits); - bits >>= 8; - bitCount -= 8; - } + /// + /// Flushes the pending buffer into the given output array. If the + /// output array is to small, only a partial flush is done. + /// + /// The output array. + /// The offset into output array. + /// The maximum number of bytes to store. + /// The number of bytes flushed. + public int Flush(byte[] output, int offset, int length) + { + if (bitCount >= 8) + { + buffer[end++] = unchecked((byte)bits); + bits >>= 8; + bitCount -= 8; + } - if (length > end - start) - { - length = end - start; - System.Array.Copy(buffer, start, output, offset, length); - start = 0; - end = 0; - } - else - { - System.Array.Copy(buffer, start, output, offset, length); - start += length; - } - return length; - } + if (length > end - start) + { + length = end - start; + System.Array.Copy(buffer, start, output, offset, length); + start = 0; + end = 0; + } + else + { + System.Array.Copy(buffer, start, output, offset, length); + start += length; + } + return length; + } - /// - /// Convert internal buffer to byte array. - /// Buffer is empty on completion - /// - /// - /// The internal buffer contents converted to a byte array. - /// - public byte[] ToByteArray() - { - AlignToByte(); + /// + /// Convert internal buffer to byte array. + /// Buffer is empty on completion + /// + /// + /// The internal buffer contents converted to a byte array. + /// + public byte[] ToByteArray() + { + AlignToByte(); - byte[] result = new byte[end - start]; - System.Array.Copy(buffer, start, result, 0, result.Length); - start = 0; - end = 0; - return result; - } - } + var result = new byte[end - start]; + System.Array.Copy(buffer, start, result, 0, result.Length); + start = 0; + end = 0; + return result; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs index 11aca7d53..7e1f9c1ec 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs @@ -3,436 +3,435 @@ using System.IO; using System.Security.Cryptography; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +/// +/// A special stream deflating or compressing the bytes that are +/// written to it. It uses a Deflater to perform actual deflating.
+/// Authors of the original java version : Tom Tromey, Jochen Hoenicke +///
+public class DeflaterOutputStream : Stream { - /// - /// A special stream deflating or compressing the bytes that are - /// written to it. It uses a Deflater to perform actual deflating.
- /// Authors of the original java version : Tom Tromey, Jochen Hoenicke - ///
- public class DeflaterOutputStream : Stream - { - #region Constructors - - /// - /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. - /// - /// - /// the output stream where deflated output should be written. - /// - public DeflaterOutputStream(Stream baseOutputStream) - : this(baseOutputStream, new Deflater(), 512) - { - } - - /// - /// Creates a new DeflaterOutputStream with the given Deflater and - /// default buffer size. - /// - /// - /// the output stream where deflated output should be written. - /// - /// - /// the underlying deflater. - /// - public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater) - : this(baseOutputStream, deflater, 512) - { - } - - /// - /// Creates a new DeflaterOutputStream with the given Deflater and - /// buffer size. - /// - /// - /// The output stream where deflated output is written. - /// - /// - /// The underlying deflater to use - /// - /// - /// The buffer size in bytes to use when deflating (minimum value 512) - /// - /// - /// bufsize is less than or equal to zero. - /// - /// - /// baseOutputStream does not support writing - /// - /// - /// deflater instance is null - /// - public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize) - { - if (baseOutputStream == null) - { - throw new ArgumentNullException(nameof(baseOutputStream)); - } - - if (baseOutputStream.CanWrite == false) - { - throw new ArgumentException("Must support writing", nameof(baseOutputStream)); - } - - if (bufferSize < 512) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - } - - baseOutputStream_ = baseOutputStream; - buffer_ = new byte[bufferSize]; - deflater_ = deflater ?? throw new ArgumentNullException(nameof(deflater)); - } - - #endregion Constructors - - #region Public API - - /// - /// Finishes the stream by calling finish() on the deflater. - /// - /// - /// Not all input is deflated - /// - public virtual void Finish() - { - deflater_.Finish(); - while (!deflater_.IsFinished) - { - int len = deflater_.Deflate(buffer_, 0, buffer_.Length); - if (len <= 0) - { - break; - } - - if (cryptoTransform_ != null) - { - EncryptBlock(buffer_, 0, len); - } - - baseOutputStream_.Write(buffer_, 0, len); - } - - if (!deflater_.IsFinished) - { - throw new SharpZipBaseException("Can't deflate all input?"); - } - - baseOutputStream_.Flush(); - - if (cryptoTransform_ != null) - { - if (cryptoTransform_ is ZipAESTransform) - { - AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); - } - cryptoTransform_.Dispose(); - cryptoTransform_ = null; - } - } - - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner { get; set; } = true; - - /// - /// Allows client to determine if an entry can be patched after its added - /// - public bool CanPatchEntries - { - get - { - return baseOutputStream_.CanSeek; - } - } - - #endregion Public API - - #region Encryption - - /// - /// The CryptoTransform currently being used to encrypt the compressed data. - /// - protected ICryptoTransform cryptoTransform_; - - /// - /// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream. - /// - protected byte[] AESAuthCode; - - /// - /// Encrypt a block of data - /// - /// - /// Data to encrypt. NOTE the original contents of the buffer are lost - /// - /// - /// Offset of first byte in buffer to encrypt - /// - /// - /// Number of bytes in buffer to encrypt - /// - protected void EncryptBlock(byte[] buffer, int offset, int length) - { - cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0); - } - - #endregion Encryption - - #region Deflation Support - - /// - /// Deflates everything in the input buffers. This will call - /// def.deflate() until all bytes from the input buffers - /// are processed. - /// - protected void Deflate() - { - Deflate(false); - } - - private void Deflate(bool flushing) - { - while (flushing || !deflater_.IsNeedingInput) - { - int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length); - - if (deflateCount <= 0) - { - break; - } - if (cryptoTransform_ != null) - { - EncryptBlock(buffer_, 0, deflateCount); - } - - baseOutputStream_.Write(buffer_, 0, deflateCount); - } - - if (!deflater_.IsNeedingInput) - { - throw new SharpZipBaseException("DeflaterOutputStream can't deflate all input?"); - } - } - - #endregion Deflation Support - - #region Stream Overrides - - /// - /// Gets value indicating stream can be read from - /// - public override bool CanRead - { - get - { - return false; - } - } - - /// - /// Gets a value indicating if seeking is supported for this stream - /// This property always returns false - /// - public override bool CanSeek - { - get - { - return false; - } - } - - /// - /// Get value indicating if this stream supports writing - /// - public override bool CanWrite - { - get - { - return baseOutputStream_.CanWrite; - } - } - - /// - /// Get current length of stream - /// - public override long Length - { - get - { - return baseOutputStream_.Length; - } - } - - /// - /// Gets the current position within the stream. - /// - /// Any attempt to set position - public override long Position - { - get - { - return baseOutputStream_.Position; - } - set - { - throw new NotSupportedException("Position property not supported"); - } - } - - /// - /// Sets the current position of this stream to the given value. Not supported by this class! - /// - /// The offset relative to the to seek. - /// The to seek from. - /// The new position in the stream. - /// Any access - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("DeflaterOutputStream Seek not supported"); - } - - /// - /// Sets the length of this stream to the given value. Not supported by this class! - /// - /// The new stream length. - /// Any access - public override void SetLength(long value) - { - throw new NotSupportedException("DeflaterOutputStream SetLength not supported"); - } - - /// - /// Read a byte from stream advancing position by one - /// - /// The byte read cast to an int. THe value is -1 if at the end of the stream. - /// Any access - public override int ReadByte() - { - throw new NotSupportedException("DeflaterOutputStream ReadByte not supported"); - } - - /// - /// Read a block of bytes from stream - /// - /// The buffer to store read data in. - /// The offset to start storing at. - /// The maximum number of bytes to read. - /// The actual number of bytes read. Zero if end of stream is detected. - /// Any access - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("DeflaterOutputStream Read not supported"); - } - - /// - /// Flushes the stream by calling Flush on the deflater and then - /// on the underlying stream. This ensures that all bytes are flushed. - /// - public override void Flush() - { - deflater_.Flush(); - Deflate(true); - baseOutputStream_.Flush(); - } - - /// - /// Calls and closes the underlying - /// stream when is true. - /// - protected override void Dispose(bool disposing) - { - if (!isClosed_) - { - isClosed_ = true; - - try - { - Finish(); - if (cryptoTransform_ != null) - { - GetAuthCodeIfAES(); - cryptoTransform_.Dispose(); - cryptoTransform_ = null; - } - } - finally - { - if (IsStreamOwner) - { - baseOutputStream_.Dispose(); - } - } - } - } - - /// - /// Get the Auth code for AES encrypted entries - /// - protected void GetAuthCodeIfAES() - { - if (cryptoTransform_ is ZipAESTransform) - { - AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); - } - } - - /// - /// Writes a single byte to the compressed output stream. - /// - /// - /// The byte value. - /// - public override void WriteByte(byte value) - { - byte[] b = new byte[1]; - b[0] = value; - Write(b, 0, 1); - } - - /// - /// Writes bytes from an array to the compressed stream. - /// - /// - /// The byte array - /// - /// - /// The offset into the byte array where to start. - /// - /// - /// The number of bytes to write. - /// - public override void Write(byte[] buffer, int offset, int count) - { - deflater_.SetInput(buffer, offset, count); - Deflate(); - } - - #endregion Stream Overrides - - #region Instance Fields - - /// - /// This buffer is used temporarily to retrieve the bytes from the - /// deflater and write them to the underlying output stream. - /// - private byte[] buffer_; - - /// - /// The deflater which is used to deflate the stream. - /// - protected Deflater deflater_; - - /// - /// Base stream the deflater depends on. - /// - protected Stream baseOutputStream_; - - private bool isClosed_; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + public DeflaterOutputStream(Stream baseOutputStream) + : this(baseOutputStream, new Deflater(), 512) + { + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// default buffer size. + /// + /// + /// the output stream where deflated output should be written. + /// + /// + /// the underlying deflater. + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater) + : this(baseOutputStream, deflater, 512) + { + } + + /// + /// Creates a new DeflaterOutputStream with the given Deflater and + /// buffer size. + /// + /// + /// The output stream where deflated output is written. + /// + /// + /// The underlying deflater to use + /// + /// + /// The buffer size in bytes to use when deflating (minimum value 512) + /// + /// + /// bufsize is less than or equal to zero. + /// + /// + /// baseOutputStream does not support writing + /// + /// + /// deflater instance is null + /// + public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize) + { + if (baseOutputStream == null) + { + throw new ArgumentNullException(nameof(baseOutputStream)); + } + + if (baseOutputStream.CanWrite == false) + { + throw new ArgumentException("Must support writing", nameof(baseOutputStream)); + } + + if (bufferSize < 512) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + baseOutputStream_ = baseOutputStream; + buffer_ = new byte[bufferSize]; + deflater_ = deflater ?? throw new ArgumentNullException(nameof(deflater)); + } + + #endregion Constructors + + #region Public API + + /// + /// Finishes the stream by calling finish() on the deflater. + /// + /// + /// Not all input is deflated + /// + public virtual void Finish() + { + deflater_.Finish(); + while (!deflater_.IsFinished) + { + var len = deflater_.Deflate(buffer_, 0, buffer_.Length); + if (len <= 0) + { + break; + } + + if (cryptoTransform_ != null) + { + EncryptBlock(buffer_, 0, len); + } + + baseOutputStream_.Write(buffer_, 0, len); + } + + if (!deflater_.IsFinished) + { + throw new SharpZipBaseException("Can't deflate all input?"); + } + + baseOutputStream_.Flush(); + + if (cryptoTransform_ != null) + { + if (cryptoTransform_ is ZipAESTransform) + { + AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); + } + cryptoTransform_.Dispose(); + cryptoTransform_ = null; + } + } + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Allows client to determine if an entry can be patched after its added + /// + public bool CanPatchEntries + { + get + { + return baseOutputStream_.CanSeek; + } + } + + #endregion Public API + + #region Encryption + + /// + /// The CryptoTransform currently being used to encrypt the compressed data. + /// + protected ICryptoTransform cryptoTransform_; + + /// + /// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream. + /// + protected byte[] AESAuthCode; + + /// + /// Encrypt a block of data + /// + /// + /// Data to encrypt. NOTE the original contents of the buffer are lost + /// + /// + /// Offset of first byte in buffer to encrypt + /// + /// + /// Number of bytes in buffer to encrypt + /// + protected void EncryptBlock(byte[] buffer, int offset, int length) + { + cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0); + } + + #endregion Encryption + + #region Deflation Support + + /// + /// Deflates everything in the input buffers. This will call + /// def.deflate() until all bytes from the input buffers + /// are processed. + /// + protected void Deflate() + { + Deflate(false); + } + + private void Deflate(bool flushing) + { + while (flushing || !deflater_.IsNeedingInput) + { + var deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length); + + if (deflateCount <= 0) + { + break; + } + if (cryptoTransform_ != null) + { + EncryptBlock(buffer_, 0, deflateCount); + } + + baseOutputStream_.Write(buffer_, 0, deflateCount); + } + + if (!deflater_.IsNeedingInput) + { + throw new SharpZipBaseException("DeflaterOutputStream can't deflate all input?"); + } + } + + #endregion Deflation Support + + #region Stream Overrides + + /// + /// Gets value indicating stream can be read from + /// + public override bool CanRead + { + get + { + return false; + } + } + + /// + /// Gets a value indicating if seeking is supported for this stream + /// This property always returns false + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Get value indicating if this stream supports writing + /// + public override bool CanWrite + { + get + { + return baseOutputStream_.CanWrite; + } + } + + /// + /// Get current length of stream + /// + public override long Length + { + get + { + return baseOutputStream_.Length; + } + } + + /// + /// Gets the current position within the stream. + /// + /// Any attempt to set position + public override long Position + { + get + { + return baseOutputStream_.Position; + } + set + { + throw new NotSupportedException("Position property not supported"); + } + } + + /// + /// Sets the current position of this stream to the given value. Not supported by this class! + /// + /// The offset relative to the to seek. + /// The to seek from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("DeflaterOutputStream Seek not supported"); + } + + /// + /// Sets the length of this stream to the given value. Not supported by this class! + /// + /// The new stream length. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("DeflaterOutputStream SetLength not supported"); + } + + /// + /// Read a byte from stream advancing position by one + /// + /// The byte read cast to an int. THe value is -1 if at the end of the stream. + /// Any access + public override int ReadByte() + { + throw new NotSupportedException("DeflaterOutputStream ReadByte not supported"); + } + + /// + /// Read a block of bytes from stream + /// + /// The buffer to store read data in. + /// The offset to start storing at. + /// The maximum number of bytes to read. + /// The actual number of bytes read. Zero if end of stream is detected. + /// Any access + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("DeflaterOutputStream Read not supported"); + } + + /// + /// Flushes the stream by calling Flush on the deflater and then + /// on the underlying stream. This ensures that all bytes are flushed. + /// + public override void Flush() + { + deflater_.Flush(); + Deflate(true); + baseOutputStream_.Flush(); + } + + /// + /// Calls and closes the underlying + /// stream when is true. + /// + protected override void Dispose(bool disposing) + { + if (!isClosed_) + { + isClosed_ = true; + + try + { + Finish(); + if (cryptoTransform_ != null) + { + GetAuthCodeIfAES(); + cryptoTransform_.Dispose(); + cryptoTransform_ = null; + } + } + finally + { + if (IsStreamOwner) + { + baseOutputStream_.Dispose(); + } + } + } + } + + /// + /// Get the Auth code for AES encrypted entries + /// + protected void GetAuthCodeIfAES() + { + if (cryptoTransform_ is ZipAESTransform) + { + AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); + } + } + + /// + /// Writes a single byte to the compressed output stream. + /// + /// + /// The byte value. + /// + public override void WriteByte(byte value) + { + var b = new byte[1]; + b[0] = value; + Write(b, 0, 1); + } + + /// + /// Writes bytes from an array to the compressed stream. + /// + /// + /// The byte array + /// + /// + /// The offset into the byte array where to start. + /// + /// + /// The number of bytes to write. + /// + public override void Write(byte[] buffer, int offset, int count) + { + deflater_.SetInput(buffer, offset, count); + Deflate(); + } + + #endregion Stream Overrides + + #region Instance Fields + + /// + /// This buffer is used temporarily to retrieve the bytes from the + /// deflater and write them to the underlying output stream. + /// + private readonly byte[] buffer_; + + /// + /// The deflater which is used to deflate the stream. + /// + protected Deflater deflater_; + + /// + /// Base stream the deflater depends on. + /// + protected Stream baseOutputStream_; + + private bool isClosed_; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs index 94e72cead..0f1f85484 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs @@ -2,712 +2,691 @@ using System.IO; using System.Security.Cryptography; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +/// +/// An input buffer customised for use by +/// +/// +/// The buffer supports decryption of incoming data. +/// +public class InflaterInputBuffer { - /// - /// An input buffer customised for use by - /// - /// - /// The buffer supports decryption of incoming data. - /// - public class InflaterInputBuffer - { - #region Constructors - - /// - /// Initialise a new instance of with a default buffer size - /// - /// The stream to buffer. - public InflaterInputBuffer(Stream stream) : this(stream, 4096) - { - } - - /// - /// Initialise a new instance of - /// - /// The stream to buffer. - /// The size to use for the buffer - /// A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB. - public InflaterInputBuffer(Stream stream, int bufferSize) - { - inputStream = stream; - if (bufferSize < 1024) - { - bufferSize = 1024; - } - rawData = new byte[bufferSize]; - clearText = rawData; - } - - #endregion Constructors - - /// - /// Get the length of bytes in the - /// - public int RawLength - { - get - { - return rawLength; - } - } - - /// - /// Get the contents of the raw data buffer. - /// - /// This may contain encrypted data. - public byte[] RawData - { - get - { - return rawData; - } - } - - /// - /// Get the number of useable bytes in - /// - public int ClearTextLength - { - get - { - return clearTextLength; - } - } - - /// - /// Get the contents of the clear text buffer. - /// - public byte[] ClearText - { - get - { - return clearText; - } - } - - /// - /// Get/set the number of bytes available - /// - public int Available - { - get { return available; } - set { available = value; } - } - - /// - /// Call passing the current clear text buffer contents. - /// - /// The inflater to set input for. - public void SetInflaterInput(Inflater inflater) - { - if (available > 0) - { - inflater.SetInput(clearText, clearTextLength - available, available); - available = 0; - } - } - - /// - /// Fill the buffer from the underlying input stream. - /// - public void Fill() - { - rawLength = 0; - int toRead = rawData.Length; - - while (toRead > 0 && inputStream.CanRead) - { - int count = inputStream.Read(rawData, rawLength, toRead); - if (count <= 0) - { - break; - } - rawLength += count; - toRead -= count; - } - - if (cryptoTransform != null) - { - clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0); - } - else - { - clearTextLength = rawLength; - } - - available = clearTextLength; - } - - /// - /// Read a buffer directly from the input stream - /// - /// The buffer to fill - /// Returns the number of bytes read. - public int ReadRawBuffer(byte[] buffer) - { - return ReadRawBuffer(buffer, 0, buffer.Length); - } - - /// - /// Read a buffer directly from the input stream - /// - /// The buffer to read into - /// The offset to start reading data into. - /// The number of bytes to read. - /// Returns the number of bytes read. - public int ReadRawBuffer(byte[] outBuffer, int offset, int length) - { - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - int currentOffset = offset; - int currentLength = length; - - while (currentLength > 0) - { - if (available <= 0) - { - Fill(); - if (available <= 0) - { - return 0; - } - } - int toCopy = Math.Min(currentLength, available); - System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy); - currentOffset += toCopy; - currentLength -= toCopy; - available -= toCopy; - } - return length; - } - - /// - /// Read clear text data from the input stream. - /// - /// The buffer to add data to. - /// The offset to start adding data at. - /// The number of bytes to read. - /// Returns the number of bytes actually read. - public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length) - { - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - int currentOffset = offset; - int currentLength = length; - - while (currentLength > 0) - { - if (available <= 0) - { - Fill(); - if (available <= 0) - { - return 0; - } - } - - int toCopy = Math.Min(currentLength, available); - Array.Copy(clearText, clearTextLength - (int)available, outBuffer, currentOffset, toCopy); - currentOffset += toCopy; - currentLength -= toCopy; - available -= toCopy; - } - return length; - } - - /// - /// Read a from the input stream. - /// - /// Returns the byte read. - public byte ReadLeByte() - { - if (available <= 0) - { - Fill(); - if (available <= 0) - { - throw new ZipException("EOF in header"); - } - } - byte result = rawData[rawLength - available]; - available -= 1; - return result; - } - - /// - /// Read an in little endian byte order. - /// - /// The short value read case to an int. - public int ReadLeShort() - { - return ReadLeByte() | (ReadLeByte() << 8); - } - - /// - /// Read an in little endian byte order. - /// - /// The int value read. - public int ReadLeInt() - { - return ReadLeShort() | (ReadLeShort() << 16); - } - - /// - /// Read a in little endian byte order. - /// - /// The long value read. - public long ReadLeLong() - { - return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); - } - - /// - /// Get/set the to apply to any data. - /// - /// Set this value to null to have no transform applied. - public ICryptoTransform CryptoTransform - { - set - { - cryptoTransform = value; - if (cryptoTransform != null) - { - if (rawData == clearText) - { - if (internalClearText == null) - { - internalClearText = new byte[rawData.Length]; - } - clearText = internalClearText; - } - clearTextLength = rawLength; - if (available > 0) - { - cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available); - } - } - else - { - clearText = rawData; - clearTextLength = rawLength; - } - } - } - - #region Instance Fields - - private int rawLength; - private byte[] rawData; - - private int clearTextLength; - private byte[] clearText; - private byte[] internalClearText; - - private int available; - - private ICryptoTransform cryptoTransform; - private Stream inputStream; - - #endregion Instance Fields - } - - /// - /// This filter stream is used to decompress data compressed using the "deflate" - /// format. The "deflate" format is described in RFC 1951. - /// - /// This stream may form the basis for other decompression filters, such - /// as the GZipInputStream. - /// - /// Author of the original java version : John Leuner. - /// - public class InflaterInputStream : Stream - { - #region Constructors - - /// - /// Create an InflaterInputStream with the default decompressor - /// and a default buffer size of 4KB. - /// - /// - /// The InputStream to read bytes from - /// - public InflaterInputStream(Stream baseInputStream) - : this(baseInputStream, new Inflater(), 4096) - { - } - - /// - /// Create an InflaterInputStream with the specified decompressor - /// and a default buffer size of 4KB. - /// - /// - /// The source of input data - /// - /// - /// The decompressor used to decompress data read from baseInputStream - /// - public InflaterInputStream(Stream baseInputStream, Inflater inf) - : this(baseInputStream, inf, 4096) - { - } - - /// - /// Create an InflaterInputStream with the specified decompressor - /// and the specified buffer size. - /// - /// - /// The InputStream to read bytes from - /// - /// - /// The decompressor to use - /// - /// - /// Size of the buffer to use - /// - public InflaterInputStream(Stream baseInputStream, Inflater inflater, int bufferSize) - { - if (baseInputStream == null) - { - throw new ArgumentNullException(nameof(baseInputStream)); - } - - if (inflater == null) - { - throw new ArgumentNullException(nameof(inflater)); - } - - if (bufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - } - - this.baseInputStream = baseInputStream; - this.inf = inflater; - - inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize); - } - - #endregion Constructors - - /// - /// Gets or sets a flag indicating ownership of underlying stream. - /// When the flag is true will close the underlying stream also. - /// - /// The default value is true. - public bool IsStreamOwner { get; set; } = true; - - /// - /// Skip specified number of bytes of uncompressed data - /// - /// - /// Number of bytes to skip - /// - /// - /// The number of bytes skipped, zero if the end of - /// stream has been reached - /// - /// - /// The number of bytes to skip is less than or equal to zero. - /// - public long Skip(long count) - { - if (count <= 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - // v0.80 Skip by seeking if underlying stream supports it... - if (baseInputStream.CanSeek) - { - baseInputStream.Seek(count, SeekOrigin.Current); - return count; - } - else - { - int length = 2048; - if (count < length) - { - length = (int)count; - } - - byte[] tmp = new byte[length]; - int readCount = 1; - long toSkip = count; - - while ((toSkip > 0) && (readCount > 0)) - { - if (toSkip < length) - { - length = (int)toSkip; - } - - readCount = baseInputStream.Read(tmp, 0, length); - toSkip -= readCount; - } - - return count - toSkip; - } - } - - /// - /// Clear any cryptographic state. - /// - protected void StopDecrypting() - { - inputBuffer.CryptoTransform = null; - } - - /// - /// Returns 0 once the end of the stream (EOF) has been reached. - /// Otherwise returns 1. - /// - public virtual int Available - { - get - { - return inf.IsFinished ? 0 : 1; - } - } - - /// - /// Fills the buffer with more data to decompress. - /// - /// - /// Stream ends early - /// - protected void Fill() - { - // Protect against redundant calls - if (inputBuffer.Available <= 0) - { - inputBuffer.Fill(); - if (inputBuffer.Available <= 0) - { - throw new SharpZipBaseException("Unexpected EOF"); - } - } - inputBuffer.SetInflaterInput(inf); - } - - #region Stream Overrides - - /// - /// Gets a value indicating whether the current stream supports reading - /// - public override bool CanRead - { - get - { - return baseInputStream.CanRead; - } - } - - /// - /// Gets a value of false indicating seeking is not supported for this stream. - /// - public override bool CanSeek - { - get - { - return false; - } - } - - /// - /// Gets a value of false indicating that this stream is not writeable. - /// - public override bool CanWrite - { - get - { - return false; - } - } - - /// - /// A value representing the length of the stream in bytes. - /// - public override long Length - { - get - { - //return inputBuffer.RawLength; - throw new NotSupportedException("InflaterInputStream Length is not supported"); - } - } - - /// - /// The current position within the stream. - /// Throws a NotSupportedException when attempting to set the position - /// - /// Attempting to set the position - public override long Position - { - get - { - return baseInputStream.Position; - } - set - { - throw new NotSupportedException("InflaterInputStream Position not supported"); - } - } - - /// - /// Flushes the baseInputStream - /// - public override void Flush() - { - baseInputStream.Flush(); - } - - /// - /// Sets the position within the current stream - /// Always throws a NotSupportedException - /// - /// The relative offset to seek to. - /// The defining where to seek from. - /// The new position in the stream. - /// Any access - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("Seek not supported"); - } - - /// - /// Set the length of the current stream - /// Always throws a NotSupportedException - /// - /// The new length value for the stream. - /// Any access - public override void SetLength(long value) - { - throw new NotSupportedException("InflaterInputStream SetLength not supported"); - } - - /// - /// Writes a sequence of bytes to stream and advances the current position - /// This method always throws a NotSupportedException - /// - /// The buffer containing data to write. - /// The offset of the first byte to write. - /// The number of bytes to write. - /// Any access - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("InflaterInputStream Write not supported"); - } - - /// - /// Writes one byte to the current stream and advances the current position - /// Always throws a NotSupportedException - /// - /// The byte to write. - /// Any access - public override void WriteByte(byte value) - { - throw new NotSupportedException("InflaterInputStream WriteByte not supported"); - } - - /// - /// Closes the input stream. When - /// is true the underlying stream is also closed. - /// - protected override void Dispose(bool disposing) - { - if (!isClosed) - { - isClosed = true; - if (IsStreamOwner) - { - baseInputStream.Dispose(); - } - } - } - - /// - /// Reads decompressed data into the provided buffer byte array - /// - /// - /// The array to read and decompress data into - /// - /// - /// The offset indicating where the data should be placed - /// - /// - /// The number of bytes to decompress - /// - /// The number of bytes read. Zero signals the end of stream - /// - /// Inflater needs a dictionary - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (inf.IsNeedingDictionary) - { - throw new SharpZipBaseException("Need a dictionary"); - } - - int remainingBytes = count; - while (true) - { - int bytesRead = inf.Inflate(buffer, offset, remainingBytes); - offset += bytesRead; - remainingBytes -= bytesRead; - - if (remainingBytes == 0 || inf.IsFinished) - { - break; - } - - if (inf.IsNeedingInput) - { - Fill(); - } - else if (bytesRead == 0) - { - throw new ZipException("Invalid input data"); - } - } - return count - remainingBytes; - } - - #endregion Stream Overrides - - #region Instance Fields - - /// - /// Decompressor for this stream - /// - protected Inflater inf; - - /// - /// Input buffer for this stream. - /// - protected InflaterInputBuffer inputBuffer; - - /// - /// Base stream the inflater reads from. - /// - private Stream baseInputStream; - - /// - /// The compressed size - /// - protected long csize; - - /// - /// Flag indicating whether this instance has been closed or not. - /// - private bool isClosed; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Initialise a new instance of with a default buffer size + /// + /// The stream to buffer. + public InflaterInputBuffer(Stream stream) : this(stream, 4096) + { + } + + /// + /// Initialise a new instance of + /// + /// The stream to buffer. + /// The size to use for the buffer + /// A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB. + public InflaterInputBuffer(Stream stream, int bufferSize) + { + inputStream = stream; + if (bufferSize < 1024) + { + bufferSize = 1024; + } + rawData = new byte[bufferSize]; + clearText = rawData; + } + + #endregion Constructors + + /// + /// Get the length of bytes in the + /// + public int RawLength + { + get + { + return rawLength; + } + } + + /// + /// Get the contents of the raw data buffer. + /// + /// This may contain encrypted data. + public byte[] RawData + { + get + { + return rawData; + } + } + + /// + /// Get the number of useable bytes in + /// + public int ClearTextLength + { + get + { + return clearTextLength; + } + } + + /// + /// Get the contents of the clear text buffer. + /// + public byte[] ClearText + { + get + { + return clearText; + } + } + + /// + /// Get/set the number of bytes available + /// + public int Available + { + get { return available; } + set { available = value; } + } + + /// + /// Call passing the current clear text buffer contents. + /// + /// The inflater to set input for. + public void SetInflaterInput(Inflater inflater) + { + if (available > 0) + { + inflater.SetInput(clearText, clearTextLength - available, available); + available = 0; + } + } + + /// + /// Fill the buffer from the underlying input stream. + /// + public void Fill() + { + rawLength = 0; + var toRead = rawData.Length; + + while (toRead > 0 && inputStream.CanRead) + { + var count = inputStream.Read(rawData, rawLength, toRead); + if (count <= 0) + { + break; + } + rawLength += count; + toRead -= count; + } + + clearTextLength = cryptoTransform != null ? cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0) : rawLength; + + available = clearTextLength; + } + + /// + /// Read a buffer directly from the input stream + /// + /// The buffer to fill + /// Returns the number of bytes read. + public int ReadRawBuffer(byte[] buffer) + { + return ReadRawBuffer(buffer, 0, buffer.Length); + } + + /// + /// Read a buffer directly from the input stream + /// + /// The buffer to read into + /// The offset to start reading data into. + /// The number of bytes to read. + /// Returns the number of bytes read. + public int ReadRawBuffer(byte[] outBuffer, int offset, int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var currentOffset = offset; + var currentLength = length; + + while (currentLength > 0) + { + if (available <= 0) + { + Fill(); + if (available <= 0) + { + return 0; + } + } + var toCopy = Math.Min(currentLength, available); + System.Array.Copy(rawData, rawLength - available, outBuffer, currentOffset, toCopy); + currentOffset += toCopy; + currentLength -= toCopy; + available -= toCopy; + } + return length; + } + + /// + /// Read clear text data from the input stream. + /// + /// The buffer to add data to. + /// The offset to start adding data at. + /// The number of bytes to read. + /// Returns the number of bytes actually read. + public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var currentOffset = offset; + var currentLength = length; + + while (currentLength > 0) + { + if (available <= 0) + { + Fill(); + if (available <= 0) + { + return 0; + } + } + + var toCopy = Math.Min(currentLength, available); + Array.Copy(clearText, clearTextLength - available, outBuffer, currentOffset, toCopy); + currentOffset += toCopy; + currentLength -= toCopy; + available -= toCopy; + } + return length; + } + + /// + /// Read a from the input stream. + /// + /// Returns the byte read. + public byte ReadLeByte() + { + if (available <= 0) + { + Fill(); + if (available <= 0) + { + throw new ZipException("EOF in header"); + } + } + var result = rawData[rawLength - available]; + available -= 1; + return result; + } + + /// + /// Read an in little endian byte order. + /// + /// The short value read case to an int. + public int ReadLeShort() + { + return ReadLeByte() | (ReadLeByte() << 8); + } + + /// + /// Read an in little endian byte order. + /// + /// The int value read. + public int ReadLeInt() + { + return ReadLeShort() | (ReadLeShort() << 16); + } + + /// + /// Read a in little endian byte order. + /// + /// The long value read. + public long ReadLeLong() + { + return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); + } + + /// + /// Get/set the to apply to any data. + /// + /// Set this value to null to have no transform applied. + public ICryptoTransform CryptoTransform + { + set + { + cryptoTransform = value; + if (cryptoTransform != null) + { + if (rawData == clearText) + { + internalClearText ??= new byte[rawData.Length]; + clearText = internalClearText; + } + clearTextLength = rawLength; + if (available > 0) + { + cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available); + } + } + else + { + clearText = rawData; + clearTextLength = rawLength; + } + } + } + + #region Instance Fields + + private int rawLength; + private readonly byte[] rawData; + + private int clearTextLength; + private byte[] clearText; + private byte[] internalClearText; + + private int available; + + private ICryptoTransform cryptoTransform; + private readonly Stream inputStream; + + #endregion Instance Fields +} + +/// +/// This filter stream is used to decompress data compressed using the "deflate" +/// format. The "deflate" format is described in RFC 1951. +/// +/// This stream may form the basis for other decompression filters, such +/// as the GZipInputStream. +/// +/// Author of the original java version : John Leuner. +/// +public class InflaterInputStream : Stream +{ + #region Constructors + + /// + /// Create an InflaterInputStream with the default decompressor + /// and a default buffer size of 4KB. + /// + /// + /// The InputStream to read bytes from + /// + public InflaterInputStream(Stream baseInputStream) + : this(baseInputStream, new Inflater(), 4096) + { + } + + /// + /// Create an InflaterInputStream with the specified decompressor + /// and a default buffer size of 4KB. + /// + /// + /// The source of input data + /// + /// + /// The decompressor used to decompress data read from baseInputStream + /// + public InflaterInputStream(Stream baseInputStream, Inflater inf) + : this(baseInputStream, inf, 4096) + { + } + + /// + /// Create an InflaterInputStream with the specified decompressor + /// and the specified buffer size. + /// + /// + /// The InputStream to read bytes from + /// + /// + /// The decompressor to use + /// + /// + /// Size of the buffer to use + /// + public InflaterInputStream(Stream baseInputStream, Inflater inflater, int bufferSize) + { + if (bufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + this.baseInputStream = baseInputStream ?? throw new ArgumentNullException(nameof(baseInputStream)); + this.inf = inflater ?? throw new ArgumentNullException(nameof(inflater)); + + inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize); + } + + #endregion Constructors + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Skip specified number of bytes of uncompressed data + /// + /// + /// Number of bytes to skip + /// + /// + /// The number of bytes skipped, zero if the end of + /// stream has been reached + /// + /// + /// The number of bytes to skip is less than or equal to zero. + /// + public long Skip(long count) + { + if (count <= 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + // v0.80 Skip by seeking if underlying stream supports it... + if (baseInputStream.CanSeek) + { + baseInputStream.Seek(count, SeekOrigin.Current); + return count; + } + else + { + var length = 2048; + if (count < length) + { + length = (int)count; + } + + var tmp = new byte[length]; + var readCount = 1; + var toSkip = count; + + while ((toSkip > 0) && (readCount > 0)) + { + if (toSkip < length) + { + length = (int)toSkip; + } + + readCount = baseInputStream.Read(tmp, 0, length); + toSkip -= readCount; + } + + return count - toSkip; + } + } + + /// + /// Clear any cryptographic state. + /// + protected void StopDecrypting() + { + inputBuffer.CryptoTransform = null; + } + + /// + /// Returns 0 once the end of the stream (EOF) has been reached. + /// Otherwise returns 1. + /// + public virtual int Available + { + get + { + return inf.IsFinished ? 0 : 1; + } + } + + /// + /// Fills the buffer with more data to decompress. + /// + /// + /// Stream ends early + /// + protected void Fill() + { + // Protect against redundant calls + if (inputBuffer.Available <= 0) + { + inputBuffer.Fill(); + if (inputBuffer.Available <= 0) + { + throw new SharpZipBaseException("Unexpected EOF"); + } + } + inputBuffer.SetInflaterInput(inf); + } + + #region Stream Overrides + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return baseInputStream.CanRead; + } + } + + /// + /// Gets a value of false indicating seeking is not supported for this stream. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value of false indicating that this stream is not writeable. + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// A value representing the length of the stream in bytes. + /// + public override long Length + { + get + { + //return inputBuffer.RawLength; + throw new NotSupportedException("InflaterInputStream Length is not supported"); + } + } + + /// + /// The current position within the stream. + /// Throws a NotSupportedException when attempting to set the position + /// + /// Attempting to set the position + public override long Position + { + get + { + return baseInputStream.Position; + } + set + { + throw new NotSupportedException("InflaterInputStream Position not supported"); + } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + baseInputStream.Flush(); + } + + /// + /// Sets the position within the current stream + /// Always throws a NotSupportedException + /// + /// The relative offset to seek to. + /// The defining where to seek from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported"); + } + + /// + /// Set the length of the current stream + /// Always throws a NotSupportedException + /// + /// The new length value for the stream. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("InflaterInputStream SetLength not supported"); + } + + /// + /// Writes a sequence of bytes to stream and advances the current position + /// This method always throws a NotSupportedException + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("InflaterInputStream Write not supported"); + } + + /// + /// Writes one byte to the current stream and advances the current position + /// Always throws a NotSupportedException + /// + /// The byte to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("InflaterInputStream WriteByte not supported"); + } + + /// + /// Closes the input stream. When + /// is true the underlying stream is also closed. + /// + protected override void Dispose(bool disposing) + { + if (!isClosed) + { + isClosed = true; + if (IsStreamOwner) + { + baseInputStream.Dispose(); + } + } + } + + /// + /// Reads decompressed data into the provided buffer byte array + /// + /// + /// The array to read and decompress data into + /// + /// + /// The offset indicating where the data should be placed + /// + /// + /// The number of bytes to decompress + /// + /// The number of bytes read. Zero signals the end of stream + /// + /// Inflater needs a dictionary + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (inf.IsNeedingDictionary) + { + throw new SharpZipBaseException("Need a dictionary"); + } + + var remainingBytes = count; + while (true) + { + var bytesRead = inf.Inflate(buffer, offset, remainingBytes); + offset += bytesRead; + remainingBytes -= bytesRead; + + if (remainingBytes == 0 || inf.IsFinished) + { + break; + } + + if (inf.IsNeedingInput) + { + Fill(); + } + else if (bytesRead == 0) + { + throw new ZipException("Invalid input data"); + } + } + return count - remainingBytes; + } + + #endregion Stream Overrides + + #region Instance Fields + + /// + /// Decompressor for this stream + /// + protected Inflater inf; + + /// + /// Input buffer for this stream. + /// + protected InflaterInputBuffer inputBuffer; + + /// + /// Base stream the inflater reads from. + /// + private readonly Stream baseInputStream; + + /// + /// The compressed size + /// + protected long csize; + + /// + /// Flag indicating whether this instance has been closed or not. + /// + private bool isClosed; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs index 1546bcb9f..892abf4e4 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs @@ -1,220 +1,215 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +/// +/// Contains the output from the Inflation process. +/// We need to have a window so that we can refer backwards into the output stream +/// to repeat stuff.
+/// Author of the original java version : John Leuner +///
+public class OutputWindow { - /// - /// Contains the output from the Inflation process. - /// We need to have a window so that we can refer backwards into the output stream - /// to repeat stuff.
- /// Author of the original java version : John Leuner - ///
- public class OutputWindow - { - #region Constants - - private const int WindowSize = 1 << 15; - private const int WindowMask = WindowSize - 1; - - #endregion Constants - - #region Instance Fields - - private byte[] window = new byte[WindowSize]; //The window is 2^15 bytes - private int windowEnd; - private int windowFilled; - - #endregion Instance Fields - - /// - /// Write a byte to this output window - /// - /// value to write - /// - /// if window is full - /// - public void Write(int value) - { - if (windowFilled++ == WindowSize) - { - throw new InvalidOperationException("Window full"); - } - window[windowEnd++] = (byte)value; - windowEnd &= WindowMask; - } - - private void SlowRepeat(int repStart, int length, int distance) - { - while (length-- > 0) - { - window[windowEnd++] = window[repStart++]; - windowEnd &= WindowMask; - repStart &= WindowMask; - } - } - - /// - /// Append a byte pattern already in the window itself - /// - /// length of pattern to copy - /// distance from end of window pattern occurs - /// - /// If the repeated data overflows the window - /// - public void Repeat(int length, int distance) - { - if ((windowFilled += length) > WindowSize) - { - throw new InvalidOperationException("Window full"); - } - - int repStart = (windowEnd - distance) & WindowMask; - int border = WindowSize - length; - if ((repStart <= border) && (windowEnd < border)) - { - if (length <= distance) - { - System.Array.Copy(window, repStart, window, windowEnd, length); - windowEnd += length; - } - else - { - // We have to copy manually, since the repeat pattern overlaps. - while (length-- > 0) - { - window[windowEnd++] = window[repStart++]; - } - } - } - else - { - SlowRepeat(repStart, length, distance); - } - } - - /// - /// Copy from input manipulator to internal window - /// - /// source of data - /// length of data to copy - /// the number of bytes copied - public int CopyStored(StreamManipulator input, int length) - { - length = Math.Min(Math.Min(length, WindowSize - windowFilled), input.AvailableBytes); - int copied; - - int tailLen = WindowSize - windowEnd; - if (length > tailLen) - { - copied = input.CopyBytes(window, windowEnd, tailLen); - if (copied == tailLen) - { - copied += input.CopyBytes(window, 0, length - tailLen); - } - } - else - { - copied = input.CopyBytes(window, windowEnd, length); - } - - windowEnd = (windowEnd + copied) & WindowMask; - windowFilled += copied; - return copied; - } - - /// - /// Copy dictionary to window - /// - /// source dictionary - /// offset of start in source dictionary - /// length of dictionary - /// - /// If window isnt empty - /// - public void CopyDict(byte[] dictionary, int offset, int length) - { - if (dictionary == null) - { - throw new ArgumentNullException(nameof(dictionary)); - } - - if (windowFilled > 0) - { - throw new InvalidOperationException(); - } - - if (length > WindowSize) - { - offset += length - WindowSize; - length = WindowSize; - } - System.Array.Copy(dictionary, offset, window, 0, length); - windowEnd = length & WindowMask; - } - - /// - /// Get remaining unfilled space in window - /// - /// Number of bytes left in window - public int GetFreeSpace() - { - return WindowSize - windowFilled; - } - - /// - /// Get bytes available for output in window - /// - /// Number of bytes filled - public int GetAvailable() - { - return windowFilled; - } - - /// - /// Copy contents of window to output - /// - /// buffer to copy to - /// offset to start at - /// number of bytes to count - /// The number of bytes copied - /// - /// If a window underflow occurs - /// - public int CopyOutput(byte[] output, int offset, int len) - { - int copyEnd = windowEnd; - if (len > windowFilled) - { - len = windowFilled; - } - else - { - copyEnd = (windowEnd - windowFilled + len) & WindowMask; - } - - int copied = len; - int tailLen = len - copyEnd; - - if (tailLen > 0) - { - System.Array.Copy(window, WindowSize - tailLen, output, offset, tailLen); - offset += tailLen; - len = copyEnd; - } - System.Array.Copy(window, copyEnd - len, output, offset, len); - windowFilled -= copied; - if (windowFilled < 0) - { - throw new InvalidOperationException(); - } - return copied; - } - - /// - /// Reset by clearing window so GetAvailable returns 0 - /// - public void Reset() - { - windowFilled = windowEnd = 0; - } - } + #region Constants + + private const int WindowSize = 1 << 15; + private const int WindowMask = WindowSize - 1; + + #endregion Constants + + #region Instance Fields + + private readonly byte[] window = new byte[WindowSize]; //The window is 2^15 bytes + private int windowEnd; + private int windowFilled; + + #endregion Instance Fields + + /// + /// Write a byte to this output window + /// + /// value to write + /// + /// if window is full + /// + public void Write(int value) + { + if (windowFilled++ == WindowSize) + { + throw new InvalidOperationException("Window full"); + } + window[windowEnd++] = (byte)value; + windowEnd &= WindowMask; + } + + private void SlowRepeat(int repStart, int length, int distance) + { + while (length-- > 0) + { + window[windowEnd++] = window[repStart++]; + windowEnd &= WindowMask; + repStart &= WindowMask; + } + } + + /// + /// Append a byte pattern already in the window itself + /// + /// length of pattern to copy + /// distance from end of window pattern occurs + /// + /// If the repeated data overflows the window + /// + public void Repeat(int length, int distance) + { + if ((windowFilled += length) > WindowSize) + { + throw new InvalidOperationException("Window full"); + } + + var repStart = (windowEnd - distance) & WindowMask; + var border = WindowSize - length; + if ((repStart <= border) && (windowEnd < border)) + { + if (length <= distance) + { + System.Array.Copy(window, repStart, window, windowEnd, length); + windowEnd += length; + } + else + { + // We have to copy manually, since the repeat pattern overlaps. + while (length-- > 0) + { + window[windowEnd++] = window[repStart++]; + } + } + } + else + { + SlowRepeat(repStart, length, distance); + } + } + + /// + /// Copy from input manipulator to internal window + /// + /// source of data + /// length of data to copy + /// the number of bytes copied + public int CopyStored(StreamManipulator input, int length) + { + length = Math.Min(Math.Min(length, WindowSize - windowFilled), input.AvailableBytes); + int copied; + + var tailLen = WindowSize - windowEnd; + if (length > tailLen) + { + copied = input.CopyBytes(window, windowEnd, tailLen); + if (copied == tailLen) + { + copied += input.CopyBytes(window, 0, length - tailLen); + } + } + else + { + copied = input.CopyBytes(window, windowEnd, length); + } + + windowEnd = (windowEnd + copied) & WindowMask; + windowFilled += copied; + return copied; + } + + /// + /// Copy dictionary to window + /// + /// source dictionary + /// offset of start in source dictionary + /// length of dictionary + /// + /// If window isnt empty + /// + public void CopyDict(byte[] dictionary, int offset, int length) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (windowFilled > 0) + { + throw new InvalidOperationException(); + } + + if (length > WindowSize) + { + offset += length - WindowSize; + length = WindowSize; + } + System.Array.Copy(dictionary, offset, window, 0, length); + windowEnd = length & WindowMask; + } + + /// + /// Get remaining unfilled space in window + /// + /// Number of bytes left in window + public int GetFreeSpace() + { + return WindowSize - windowFilled; + } + + /// + /// Get bytes available for output in window + /// + /// Number of bytes filled + public int GetAvailable() + { + return windowFilled; + } + + /// + /// Copy contents of window to output + /// + /// buffer to copy to + /// offset to start at + /// number of bytes to count + /// The number of bytes copied + /// + /// If a window underflow occurs + /// + public int CopyOutput(byte[] output, int offset, int len) + { + var copyEnd = windowEnd; + if (len > windowFilled) + { + len = windowFilled; + } + else + { + copyEnd = (windowEnd - windowFilled + len) & WindowMask; + } + + var copied = len; + var tailLen = len - copyEnd; + + if (tailLen > 0) + { + System.Array.Copy(window, WindowSize - tailLen, output, offset, tailLen); + offset += tailLen; + len = copyEnd; + } + System.Array.Copy(window, copyEnd - len, output, offset, len); + windowFilled -= copied; + return windowFilled < 0 ? throw new InvalidOperationException() : copied; + } + + /// + /// Reset by clearing window so GetAvailable returns 0 + /// + public void Reset() + { + windowFilled = windowEnd = 0; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs index 3626e3c0b..58f276b97 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs @@ -1,298 +1,297 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; + +/// +/// This class allows us to retrieve a specified number of bits from +/// the input buffer, as well as copy big byte blocks. +/// +/// It uses an int buffer to store up to 31 bits for direct +/// manipulation. This guarantees that we can get at least 16 bits, +/// but we only need at most 15, so this is all safe. +/// +/// There are some optimizations in this class, for example, you must +/// never peek more than 8 bits more than needed, and you must first +/// peek bits before you may drop them. This is not a general purpose +/// class but optimized for the behaviour of the Inflater. +/// +/// authors of the original java version : John Leuner, Jochen Hoenicke +/// +public class StreamManipulator { - /// - /// This class allows us to retrieve a specified number of bits from - /// the input buffer, as well as copy big byte blocks. - /// - /// It uses an int buffer to store up to 31 bits for direct - /// manipulation. This guarantees that we can get at least 16 bits, - /// but we only need at most 15, so this is all safe. - /// - /// There are some optimizations in this class, for example, you must - /// never peek more than 8 bits more than needed, and you must first - /// peek bits before you may drop them. This is not a general purpose - /// class but optimized for the behaviour of the Inflater. - /// - /// authors of the original java version : John Leuner, Jochen Hoenicke - /// - public class StreamManipulator - { - /// - /// Get the next sequence of bits but don't increase input pointer. bitCount must be - /// less or equal 16 and if this call succeeds, you must drop - /// at least n - 8 bits in the next call. - /// - /// The number of bits to peek. - /// - /// the value of the bits, or -1 if not enough bits available. */ - /// - public int PeekBits(int bitCount) - { - if (bitsInBuffer_ < bitCount) - { - if (windowStart_ == windowEnd_) - { - return -1; // ok - } - buffer_ |= (uint)((window_[windowStart_++] & 0xff | - (window_[windowStart_++] & 0xff) << 8) << bitsInBuffer_); - bitsInBuffer_ += 16; - } - return (int)(buffer_ & ((1 << bitCount) - 1)); - } + /// + /// Get the next sequence of bits but don't increase input pointer. bitCount must be + /// less or equal 16 and if this call succeeds, you must drop + /// at least n - 8 bits in the next call. + /// + /// The number of bits to peek. + /// + /// the value of the bits, or -1 if not enough bits available. */ + /// + public int PeekBits(int bitCount) + { + if (bitsInBuffer_ < bitCount) + { + if (windowStart_ == windowEnd_) + { + return -1; // ok + } + buffer_ |= (uint)(((window_[windowStart_++] & 0xff) | + ((window_[windowStart_++] & 0xff) << 8)) << bitsInBuffer_); + bitsInBuffer_ += 16; + } + return (int)(buffer_ & ((1 << bitCount) - 1)); + } - /// - /// Tries to grab the next bits from the input and - /// sets to the value, adding . - /// - /// true if enough bits could be read, otherwise false - public bool TryGetBits(int bitCount, ref int output, int outputOffset = 0) - { - var bits = PeekBits(bitCount); - if (bits < 0) - { - return false; - } - output = bits + outputOffset; - DropBits(bitCount); - return true; - } + /// + /// Tries to grab the next bits from the input and + /// sets to the value, adding . + /// + /// true if enough bits could be read, otherwise false + public bool TryGetBits(int bitCount, ref int output, int outputOffset = 0) + { + var bits = PeekBits(bitCount); + if (bits < 0) + { + return false; + } + output = bits + outputOffset; + DropBits(bitCount); + return true; + } - /// - /// Tries to grab the next bits from the input and - /// sets of to the value. - /// - /// true if enough bits could be read, otherwise false - public bool TryGetBits(int bitCount, ref byte[] array, int index) - { - var bits = PeekBits(bitCount); - if (bits < 0) - { - return false; - } - array[index] = (byte)bits; - DropBits(bitCount); - return true; - } + /// + /// Tries to grab the next bits from the input and + /// sets of to the value. + /// + /// true if enough bits could be read, otherwise false + public bool TryGetBits(int bitCount, ref byte[] array, int index) + { + var bits = PeekBits(bitCount); + if (bits < 0) + { + return false; + } + array[index] = (byte)bits; + DropBits(bitCount); + return true; + } - /// - /// Drops the next n bits from the input. You should have called PeekBits - /// with a bigger or equal n before, to make sure that enough bits are in - /// the bit buffer. - /// - /// The number of bits to drop. - public void DropBits(int bitCount) - { - buffer_ >>= bitCount; - bitsInBuffer_ -= bitCount; - } + /// + /// Drops the next n bits from the input. You should have called PeekBits + /// with a bigger or equal n before, to make sure that enough bits are in + /// the bit buffer. + /// + /// The number of bits to drop. + public void DropBits(int bitCount) + { + buffer_ >>= bitCount; + bitsInBuffer_ -= bitCount; + } - /// - /// Gets the next n bits and increases input pointer. This is equivalent - /// to followed by , except for correct error handling. - /// - /// The number of bits to retrieve. - /// - /// the value of the bits, or -1 if not enough bits available. - /// - public int GetBits(int bitCount) - { - int bits = PeekBits(bitCount); - if (bits >= 0) - { - DropBits(bitCount); - } - return bits; - } + /// + /// Gets the next n bits and increases input pointer. This is equivalent + /// to followed by , except for correct error handling. + /// + /// The number of bits to retrieve. + /// + /// the value of the bits, or -1 if not enough bits available. + /// + public int GetBits(int bitCount) + { + var bits = PeekBits(bitCount); + if (bits >= 0) + { + DropBits(bitCount); + } + return bits; + } - /// - /// Gets the number of bits available in the bit buffer. This must be - /// only called when a previous PeekBits() returned -1. - /// - /// - /// the number of bits available. - /// - public int AvailableBits - { - get - { - return bitsInBuffer_; - } - } + /// + /// Gets the number of bits available in the bit buffer. This must be + /// only called when a previous PeekBits() returned -1. + /// + /// + /// the number of bits available. + /// + public int AvailableBits + { + get + { + return bitsInBuffer_; + } + } - /// - /// Gets the number of bytes available. - /// - /// - /// The number of bytes available. - /// - public int AvailableBytes - { - get - { - return windowEnd_ - windowStart_ + (bitsInBuffer_ >> 3); - } - } + /// + /// Gets the number of bytes available. + /// + /// + /// The number of bytes available. + /// + public int AvailableBytes + { + get + { + return windowEnd_ - windowStart_ + (bitsInBuffer_ >> 3); + } + } - /// - /// Skips to the next byte boundary. - /// - public void SkipToByteBoundary() - { - buffer_ >>= (bitsInBuffer_ & 7); - bitsInBuffer_ &= ~7; - } + /// + /// Skips to the next byte boundary. + /// + public void SkipToByteBoundary() + { + buffer_ >>= bitsInBuffer_ & 7; + bitsInBuffer_ &= ~7; + } - /// - /// Returns true when SetInput can be called - /// - public bool IsNeedingInput - { - get - { - return windowStart_ == windowEnd_; - } - } + /// + /// Returns true when SetInput can be called + /// + public bool IsNeedingInput + { + get + { + return windowStart_ == windowEnd_; + } + } - /// - /// Copies bytes from input buffer to output buffer starting - /// at output[offset]. You have to make sure, that the buffer is - /// byte aligned. If not enough bytes are available, copies fewer - /// bytes. - /// - /// - /// The buffer to copy bytes to. - /// - /// - /// The offset in the buffer at which copying starts - /// - /// - /// The length to copy, 0 is allowed. - /// - /// - /// The number of bytes copied, 0 if no bytes were available. - /// - /// - /// Length is less than zero - /// - /// - /// Bit buffer isnt byte aligned - /// - public int CopyBytes(byte[] output, int offset, int length) - { - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } + /// + /// Copies bytes from input buffer to output buffer starting + /// at output[offset]. You have to make sure, that the buffer is + /// byte aligned. If not enough bytes are available, copies fewer + /// bytes. + /// + /// + /// The buffer to copy bytes to. + /// + /// + /// The offset in the buffer at which copying starts + /// + /// + /// The length to copy, 0 is allowed. + /// + /// + /// The number of bytes copied, 0 if no bytes were available. + /// + /// + /// Length is less than zero + /// + /// + /// Bit buffer isnt byte aligned + /// + public int CopyBytes(byte[] output, int offset, int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } - if ((bitsInBuffer_ & 7) != 0) - { - // bits_in_buffer may only be 0 or a multiple of 8 - throw new InvalidOperationException("Bit buffer is not byte aligned!"); - } + if ((bitsInBuffer_ & 7) != 0) + { + // bits_in_buffer may only be 0 or a multiple of 8 + throw new InvalidOperationException("Bit buffer is not byte aligned!"); + } - int count = 0; - while ((bitsInBuffer_ > 0) && (length > 0)) - { - output[offset++] = (byte)buffer_; - buffer_ >>= 8; - bitsInBuffer_ -= 8; - length--; - count++; - } + var count = 0; + while ((bitsInBuffer_ > 0) && (length > 0)) + { + output[offset++] = (byte)buffer_; + buffer_ >>= 8; + bitsInBuffer_ -= 8; + length--; + count++; + } - if (length == 0) - { - return count; - } + if (length == 0) + { + return count; + } - int avail = windowEnd_ - windowStart_; - if (length > avail) - { - length = avail; - } - System.Array.Copy(window_, windowStart_, output, offset, length); - windowStart_ += length; + var avail = windowEnd_ - windowStart_; + if (length > avail) + { + length = avail; + } + System.Array.Copy(window_, windowStart_, output, offset, length); + windowStart_ += length; - if (((windowStart_ - windowEnd_) & 1) != 0) - { - // We always want an even number of bytes in input, see peekBits - buffer_ = (uint)(window_[windowStart_++] & 0xff); - bitsInBuffer_ = 8; - } - return count + length; - } + if (((windowStart_ - windowEnd_) & 1) != 0) + { + // We always want an even number of bytes in input, see peekBits + buffer_ = (uint)(window_[windowStart_++] & 0xff); + bitsInBuffer_ = 8; + } + return count + length; + } - /// - /// Resets state and empties internal buffers - /// - public void Reset() - { - buffer_ = 0; - windowStart_ = windowEnd_ = bitsInBuffer_ = 0; - } + /// + /// Resets state and empties internal buffers + /// + public void Reset() + { + buffer_ = 0; + windowStart_ = windowEnd_ = bitsInBuffer_ = 0; + } - /// - /// Add more input for consumption. - /// Only call when IsNeedingInput returns true - /// - /// data to be input - /// offset of first byte of input - /// number of bytes of input to add. - public void SetInput(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + /// + /// Add more input for consumption. + /// Only call when IsNeedingInput returns true + /// + /// data to be input + /// offset of first byte of input + /// number of bytes of input to add. + public void SetInput(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); - } + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); + } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); - } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); + } - if (windowStart_ < windowEnd_) - { - throw new InvalidOperationException("Old input was not completely processed"); - } + if (windowStart_ < windowEnd_) + { + throw new InvalidOperationException("Old input was not completely processed"); + } - int end = offset + count; + var end = offset + count; - // We want to throw an ArrayIndexOutOfBoundsException early. - // Note the check also handles integer wrap around. - if ((offset > end) || (end > buffer.Length)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } + // We want to throw an ArrayIndexOutOfBoundsException early. + // Note the check also handles integer wrap around. + if ((offset > end) || (end > buffer.Length)) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } - if ((count & 1) != 0) - { - // We always want an even number of bytes in input, see PeekBits - buffer_ |= (uint)((buffer[offset++] & 0xff) << bitsInBuffer_); - bitsInBuffer_ += 8; - } + if ((count & 1) != 0) + { + // We always want an even number of bytes in input, see PeekBits + buffer_ |= (uint)((buffer[offset++] & 0xff) << bitsInBuffer_); + bitsInBuffer_ += 8; + } - window_ = buffer; - windowStart_ = offset; - windowEnd_ = end; - } + window_ = buffer; + windowStart_ = offset; + windowEnd_ = end; + } - #region Instance Fields + #region Instance Fields - private byte[] window_; - private int windowStart_; - private int windowEnd_; + private byte[] window_; + private int windowStart_; + private int windowEnd_; - private uint buffer_; - private int bitsInBuffer_; + private uint buffer_; + private int bitsInBuffer_; - #endregion Instance Fields - } + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs index 6649a0a23..c1bf60c31 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs @@ -5,971 +5,931 @@ using static MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Deflater; using static MelonLoader.ICSharpCode.SharpZipLib.Zip.ZipEntryFactory; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// FastZipEvents supports all events applicable to FastZip operations. +/// +public class FastZipEvents +{ + /// + /// Delegate to invoke when processing directories. + /// + public event EventHandler ProcessDirectory; + + /// + /// Delegate to invoke when processing files. + /// + public ProcessFileHandler ProcessFile; + + /// + /// Delegate to invoke during processing of files. + /// + public ProgressHandler Progress; + + /// + /// Delegate to invoke when processing for a file has been completed. + /// + public CompletedFileHandler CompletedFile; + + /// + /// Delegate to invoke when processing directory failures. + /// + public DirectoryFailureHandler DirectoryFailure; + + /// + /// Delegate to invoke when processing file failures. + /// + public FileFailureHandler FileFailure; + + /// + /// Raise the directory failure event. + /// + /// The directory causing the failure. + /// The exception for this event. + /// A boolean indicating if execution should continue or not. + public bool OnDirectoryFailure(string directory, Exception e) + { + var result = false; + var handler = DirectoryFailure; + + if (handler != null) + { + var args = new ScanFailureEventArgs(directory, e); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the file failure handler delegate. + /// + /// The file causing the failure. + /// The exception for this failure. + /// A boolean indicating if execution should continue or not. + public bool OnFileFailure(string file, Exception e) + { + var handler = FileFailure; + var result = handler != null; + + if (result) + { + var args = new ScanFailureEventArgs(file, e); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the ProcessFile delegate. + /// + /// The file being processed. + /// A boolean indicating if execution should continue or not. + public bool OnProcessFile(string file) + { + var result = true; + var handler = ProcessFile; + + if (handler != null) + { + var args = new ScanEventArgs(file); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the delegate + /// + /// The file whose processing has been completed. + /// A boolean indicating if execution should continue or not. + public bool OnCompletedFile(string file) + { + var result = true; + var handler = CompletedFile; + if (handler != null) + { + var args = new ScanEventArgs(file); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// Fires the process directory delegate. + /// + /// The directory being processed. + /// Flag indicating if the directory has matching files as determined by the current filter. + /// A of true if the operation should continue; false otherwise. + public bool OnProcessDirectory(string directory, bool hasMatchingFiles) + { + var result = true; + var handler = ProcessDirectory; + if (handler != null) + { + var args = new DirectoryEventArgs(directory, hasMatchingFiles); + handler(this, args); + result = args.ContinueRunning; + } + return result; + } + + /// + /// The minimum timespan between events. + /// + /// The minimum period of time between events. + /// + /// The default interval is three seconds. + public TimeSpan ProgressInterval + { + get { return progressInterval_; } + set { progressInterval_ = value; } + } + + #region Instance Fields + + private TimeSpan progressInterval_ = TimeSpan.FromSeconds(3); + + #endregion Instance Fields +} + +/// +/// FastZip provides facilities for creating and extracting zip files. +/// +public class FastZip { - /// - /// FastZipEvents supports all events applicable to FastZip operations. - /// - public class FastZipEvents - { - /// - /// Delegate to invoke when processing directories. - /// - public event EventHandler ProcessDirectory; - - /// - /// Delegate to invoke when processing files. - /// - public ProcessFileHandler ProcessFile; - - /// - /// Delegate to invoke during processing of files. - /// - public ProgressHandler Progress; - - /// - /// Delegate to invoke when processing for a file has been completed. - /// - public CompletedFileHandler CompletedFile; - - /// - /// Delegate to invoke when processing directory failures. - /// - public DirectoryFailureHandler DirectoryFailure; - - /// - /// Delegate to invoke when processing file failures. - /// - public FileFailureHandler FileFailure; - - /// - /// Raise the directory failure event. - /// - /// The directory causing the failure. - /// The exception for this event. - /// A boolean indicating if execution should continue or not. - public bool OnDirectoryFailure(string directory, Exception e) - { - bool result = false; - DirectoryFailureHandler handler = DirectoryFailure; - - if (handler != null) - { - var args = new ScanFailureEventArgs(directory, e); - handler(this, args); - result = args.ContinueRunning; - } - return result; - } - - /// - /// Fires the file failure handler delegate. - /// - /// The file causing the failure. - /// The exception for this failure. - /// A boolean indicating if execution should continue or not. - public bool OnFileFailure(string file, Exception e) - { - FileFailureHandler handler = FileFailure; - bool result = (handler != null); - - if (result) - { - var args = new ScanFailureEventArgs(file, e); - handler(this, args); - result = args.ContinueRunning; - } - return result; - } - - /// - /// Fires the ProcessFile delegate. - /// - /// The file being processed. - /// A boolean indicating if execution should continue or not. - public bool OnProcessFile(string file) - { - bool result = true; - ProcessFileHandler handler = ProcessFile; - - if (handler != null) - { - var args = new ScanEventArgs(file); - handler(this, args); - result = args.ContinueRunning; - } - return result; - } - - /// - /// Fires the delegate - /// - /// The file whose processing has been completed. - /// A boolean indicating if execution should continue or not. - public bool OnCompletedFile(string file) - { - bool result = true; - CompletedFileHandler handler = CompletedFile; - if (handler != null) - { - var args = new ScanEventArgs(file); - handler(this, args); - result = args.ContinueRunning; - } - return result; - } - - /// - /// Fires the process directory delegate. - /// - /// The directory being processed. - /// Flag indicating if the directory has matching files as determined by the current filter. - /// A of true if the operation should continue; false otherwise. - public bool OnProcessDirectory(string directory, bool hasMatchingFiles) - { - bool result = true; - EventHandler handler = ProcessDirectory; - if (handler != null) - { - var args = new DirectoryEventArgs(directory, hasMatchingFiles); - handler(this, args); - result = args.ContinueRunning; - } - return result; - } - - /// - /// The minimum timespan between events. - /// - /// The minimum period of time between events. - /// - /// The default interval is three seconds. - public TimeSpan ProgressInterval - { - get { return progressInterval_; } - set { progressInterval_ = value; } - } - - #region Instance Fields - - private TimeSpan progressInterval_ = TimeSpan.FromSeconds(3); - - #endregion Instance Fields - } - - /// - /// FastZip provides facilities for creating and extracting zip files. - /// - public class FastZip - { - #region Enumerations - - /// - /// Defines the desired handling when overwriting files during extraction. - /// - public enum Overwrite - { - /// - /// Prompt the user to confirm overwriting - /// - Prompt, - - /// - /// Never overwrite files. - /// - Never, - - /// - /// Always overwrite files. - /// - Always - } - - #endregion Enumerations - - #region Constructors - - /// - /// Initialise a default instance of . - /// - public FastZip() - { - } - - /// - /// Initialise a new instance of using the specified - /// - /// The time setting to use when creating or extracting Zip entries. - /// Using TimeSetting.LastAccessTime[Utc] when - /// creating an archive will set the file time to the moment of reading. - /// - public FastZip(TimeSetting timeSetting) - { - entryFactory_ = new ZipEntryFactory(timeSetting); - restoreDateTimeOnExtract_ = true; - } - - /// - /// Initialise a new instance of using the specified - /// - /// The time to set all values for created or extracted Zip Entries. - public FastZip(DateTime time) - { - entryFactory_ = new ZipEntryFactory(time); - restoreDateTimeOnExtract_ = true; - } - - /// - /// Initialise a new instance of - /// - /// The events to use during operations. - public FastZip(FastZipEvents events) - { - events_ = events; - } - - #endregion Constructors - - #region Properties - - /// - /// Get/set a value indicating whether empty directories should be created. - /// - public bool CreateEmptyDirectories - { - get { return createEmptyDirectories_; } - set { createEmptyDirectories_ = value; } - } - - /// - /// Get / set the password value. - /// - public string Password - { - get { return password_; } - set { password_ = value; } - } - - /// - /// Get / set the method of encrypting entries. - /// - /// - /// Only applies when is set. - /// Defaults to ZipCrypto for backwards compatibility purposes. - /// - public ZipEncryptionMethod EntryEncryptionMethod { get; set; } = ZipEncryptionMethod.ZipCrypto; - - /// - /// Get or set the active when creating Zip files. - /// - /// - public INameTransform NameTransform - { - get { return entryFactory_.NameTransform; } - set - { - entryFactory_.NameTransform = value; - } - } - - /// - /// Get or set the active when creating Zip files. - /// - public IEntryFactory EntryFactory - { - get { return entryFactory_; } - set - { - if (value == null) - { - entryFactory_ = new ZipEntryFactory(); - } - else - { - entryFactory_ = value; - } - } - } - - /// - /// Gets or sets the setting for Zip64 handling when writing. - /// - /// - /// The default value is dynamic which is not backwards compatible with old - /// programs and can cause problems with XP's built in compression which cant - /// read Zip64 archives. However it does avoid the situation were a large file - /// is added and cannot be completed correctly. - /// NOTE: Setting the size for entries before they are added is the best solution! - /// By default the EntryFactory used by FastZip will set the file size. - /// - public UseZip64 UseZip64 - { - get { return useZip64_; } - set { useZip64_ = value; } - } - - /// - /// Get/set a value indicating whether file dates and times should - /// be restored when extracting files from an archive. - /// - /// The default value is false. - public bool RestoreDateTimeOnExtract - { - get - { - return restoreDateTimeOnExtract_; - } - set - { - restoreDateTimeOnExtract_ = value; - } - } - - /// - /// Get/set a value indicating whether file attributes should - /// be restored during extract operations - /// - public bool RestoreAttributesOnExtract - { - get { return restoreAttributesOnExtract_; } - set { restoreAttributesOnExtract_ = value; } - } - - /// - /// Get/set the Compression Level that will be used - /// when creating the zip - /// - public Deflater.CompressionLevel CompressionLevel - { - get { return compressionLevel_; } - set { compressionLevel_ = value; } - } - - #endregion Properties - - #region Delegates - - /// - /// Delegate called when confirming overwriting of files. - /// - public delegate bool ConfirmOverwriteDelegate(string fileName); - - #endregion Delegates - - #region CreateZip - - /// - /// Create a zip file. - /// - /// The name of the zip file to create. - /// The directory to source files from. - /// True to recurse directories, false for no recursion. - /// The file filter to apply. - /// The directory filter to apply. - public void CreateZip(string zipFileName, string sourceDirectory, - bool recurse, string fileFilter, string directoryFilter) - { - CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter); - } - - /// - /// Create a zip file/archive. - /// - /// The name of the zip file to create. - /// The directory to obtain files and directories from. - /// True to recurse directories, false for no recursion. - /// The file filter to apply. - public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter) - { - CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null); - } - - /// - /// Create a zip archive sending output to the passed. - /// - /// The stream to write archive data to. - /// The directory to source files from. - /// True to recurse directories, false for no recursion. - /// The file filter to apply. - /// The directory filter to apply. - /// The is closed after creation. - public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter) - { - CreateZip(outputStream, sourceDirectory, recurse, fileFilter, directoryFilter, false); - } - - /// - /// Create a zip archive sending output to the passed. - /// - /// The stream to write archive data to. - /// The directory to source files from. - /// True to recurse directories, false for no recursion. - /// The file filter to apply. - /// The directory filter to apply. - /// true to leave open after the zip has been created, false to dispose it. - public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter, bool leaveOpen) - { - var scanner = new FileSystemScanner(fileFilter, directoryFilter); - CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen); - } - - /// - /// Create a zip file. - /// - /// The name of the zip file to create. - /// The directory to source files from. - /// True to recurse directories, false for no recursion. - /// The file filter to apply. - /// The directory filter to apply. - public void CreateZip(string zipFileName, string sourceDirectory, - bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter) - { - CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter, false); - } - - /// - /// Create a zip archive sending output to the passed. - /// - /// The stream to write archive data to. - /// The directory to source files from. - /// True to recurse directories, false for no recursion. - /// The file filter to apply. - /// The directory filter to apply. - /// true to leave open after the zip has been created, false to dispose it. - public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter, bool leaveOpen = false) - { - var scanner = new FileSystemScanner(fileFilter, directoryFilter); - CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen); - } - - /// - /// Create a zip archive sending output to the passed. - /// - /// The stream to write archive data to. - /// The directory to source files from. - /// True to recurse directories, false for no recursion. - /// For performing the actual file system scan - /// true to leave open after the zip has been created, false to dispose it. - /// The is closed after creation. - private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, FileSystemScanner scanner, bool leaveOpen) - { - NameTransform = new ZipNameTransform(sourceDirectory); - sourceDirectory_ = sourceDirectory; - - using (outputStream_ = new ZipOutputStream(outputStream)) - { - outputStream_.SetLevel((int)CompressionLevel); - outputStream_.IsStreamOwner = !leaveOpen; - outputStream_.NameTransform = null; // all required transforms handled by us - - if (false == string.IsNullOrEmpty(password_) && EntryEncryptionMethod != ZipEncryptionMethod.None) - { - outputStream_.Password = password_; - } - - outputStream_.UseZip64 = UseZip64; - scanner.ProcessFile += ProcessFile; - if (this.CreateEmptyDirectories) - { - scanner.ProcessDirectory += ProcessDirectory; - } - - if (events_ != null) - { - if (events_.FileFailure != null) - { - scanner.FileFailure += events_.FileFailure; - } - - if (events_.DirectoryFailure != null) - { - scanner.DirectoryFailure += events_.DirectoryFailure; - } - } - - scanner.Scan(sourceDirectory, recurse); - } - } - - #endregion CreateZip - - #region ExtractZip - - /// - /// Extract the contents of a zip file. - /// - /// The zip file to extract from. - /// The directory to save extracted information in. - /// A filter to apply to files. - public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) - { - ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_); - } - - /// - /// Extract the contents of a zip file. - /// - /// The zip file to extract from. - /// The directory to save extracted information in. - /// The style of overwriting to apply. - /// A delegate to invoke when confirming overwriting. - /// A filter to apply to files. - /// A filter to apply to directories. - /// Flag indicating whether to restore the date and time for extracted files. - /// Allow parent directory traversal in file paths (e.g. ../file) - public void ExtractZip(string zipFileName, string targetDirectory, - Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, - string fileFilter, string directoryFilter, bool restoreDateTime, bool allowParentTraversal = false) - { - Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read); - ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, true, allowParentTraversal); - } - - /// - /// Extract the contents of a zip file held in a stream. - /// - /// The seekable input stream containing the zip to extract from. - /// The directory to save extracted information in. - /// The style of overwriting to apply. - /// A delegate to invoke when confirming overwriting. - /// A filter to apply to files. - /// A filter to apply to directories. - /// Flag indicating whether to restore the date and time for extracted files. - /// Flag indicating whether the inputStream will be closed by this method. - /// Allow parent directory traversal in file paths (e.g. ../file) - public void ExtractZip(Stream inputStream, string targetDirectory, - Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, - string fileFilter, string directoryFilter, bool restoreDateTime, - bool isStreamOwner, bool allowParentTraversal = false) - { - if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null)) - { - throw new ArgumentNullException(nameof(confirmDelegate)); - } - - continueRunning_ = true; - overwrite_ = overwrite; - confirmDelegate_ = confirmDelegate; - extractNameTransform_ = new WindowsNameTransform(targetDirectory, allowParentTraversal); - - fileFilter_ = new NameFilter(fileFilter); - directoryFilter_ = new NameFilter(directoryFilter); - restoreDateTimeOnExtract_ = restoreDateTime; - - using (zipFile_ = new ZipFile(inputStream, !isStreamOwner)) - { - if (password_ != null) - { - zipFile_.Password = password_; - } - - System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator(); - while (continueRunning_ && enumerator.MoveNext()) - { - var entry = (ZipEntry)enumerator.Current; - if (entry.IsFile) - { - // TODO Path.GetDirectory can fail here on invalid characters. - if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name)) - { - ExtractEntry(entry); - } - } - else if (entry.IsDirectory) - { - if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories) - { - ExtractEntry(entry); - } - } - else - { - // Do nothing for volume labels etc... - } - } - } - } - - #endregion ExtractZip - - #region Internal Processing - - private void ProcessDirectory(object sender, DirectoryEventArgs e) - { - if (!e.HasMatchingFiles && CreateEmptyDirectories) - { - if (events_ != null) - { - events_.OnProcessDirectory(e.Name, e.HasMatchingFiles); - } - - if (e.ContinueRunning) - { - if (e.Name != sourceDirectory_) - { - ZipEntry entry = entryFactory_.MakeDirectoryEntry(e.Name); - outputStream_.PutNextEntry(entry); - } - } - } - } - - private void ProcessFile(object sender, ScanEventArgs e) - { - if ((events_ != null) && (events_.ProcessFile != null)) - { - events_.ProcessFile(sender, e); - } - - if (e.ContinueRunning) - { - try - { - // The open below is equivalent to OpenRead which guarantees that if opened the - // file will not be changed by subsequent openers, but precludes opening in some cases - // were it could succeed. ie the open may fail as its already open for writing and the share mode should reflect that. - using (FileStream stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - ZipEntry entry = entryFactory_.MakeFileEntry(e.Name); - - // Set up AES encryption for the entry if required. - ConfigureEntryEncryption(entry); - - outputStream_.PutNextEntry(entry); - AddFileContents(e.Name, stream); - } - } - catch (Exception ex) - { - if (events_ != null) - { - continueRunning_ = events_.OnFileFailure(e.Name, ex); - } - else - { - continueRunning_ = false; - throw; - } - } - } - } - - // Set up the encryption method to use for the specific entry. - private void ConfigureEntryEncryption(ZipEntry entry) - { - // Only alter the entries options if AES isn't already enabled for it - // (it might have been set up by the entry factory, and if so we let that take precedence) - if (!string.IsNullOrEmpty(Password) && entry.AESEncryptionStrength == 0) - { - switch (EntryEncryptionMethod) - { - case ZipEncryptionMethod.AES128: - entry.AESKeySize = 128; - break; - - case ZipEncryptionMethod.AES256: - entry.AESKeySize = 256; - break; - } - } - } - - private void AddFileContents(string name, Stream stream) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (buffer_ == null) - { - buffer_ = new byte[4096]; - } - - if ((events_ != null) && (events_.Progress != null)) - { - StreamUtils.Copy(stream, outputStream_, buffer_, - events_.Progress, events_.ProgressInterval, this, name); - } - else - { - StreamUtils.Copy(stream, outputStream_, buffer_); - } - - if (events_ != null) - { - continueRunning_ = events_.OnCompletedFile(name); - } - } - - private void ExtractFileEntry(ZipEntry entry, string targetName) - { - bool proceed = true; - if (overwrite_ != Overwrite.Always) - { - if (File.Exists(targetName)) - { - if ((overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null)) - { - proceed = confirmDelegate_(targetName); - } - else - { - proceed = false; - } - } - } - - if (proceed) - { - if (events_ != null) - { - continueRunning_ = events_.OnProcessFile(entry.Name); - } - - if (continueRunning_) - { - try - { - using (FileStream outputStream = File.Create(targetName)) - { - if (buffer_ == null) - { - buffer_ = new byte[4096]; - } - - using (var inputStream = zipFile_.GetInputStream(entry)) - { - if ((events_ != null) && (events_.Progress != null)) - { - StreamUtils.Copy(inputStream, outputStream, buffer_, - events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size); - } - else - { - StreamUtils.Copy(inputStream, outputStream, buffer_); - } - } - - if (events_ != null) - { - continueRunning_ = events_.OnCompletedFile(entry.Name); - } - } - - if (restoreDateTimeOnExtract_) - { - switch (entryFactory_.Setting) - { - case TimeSetting.CreateTime: - File.SetCreationTime(targetName, entry.DateTime); - break; - - case TimeSetting.CreateTimeUtc: - File.SetCreationTimeUtc(targetName, entry.DateTime); - break; - - case TimeSetting.LastAccessTime: - File.SetLastAccessTime(targetName, entry.DateTime); - break; - - case TimeSetting.LastAccessTimeUtc: - File.SetLastAccessTimeUtc(targetName, entry.DateTime); - break; - - case TimeSetting.LastWriteTime: - File.SetLastWriteTime(targetName, entry.DateTime); - break; - - case TimeSetting.LastWriteTimeUtc: - File.SetLastWriteTimeUtc(targetName, entry.DateTime); - break; - - case TimeSetting.Fixed: - File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime); - break; - - default: - throw new ZipException("Unhandled time setting in ExtractFileEntry"); - } - } - - if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) - { - var fileAttributes = (FileAttributes)entry.ExternalFileAttributes; - // TODO: FastZip - Setting of other file attributes on extraction is a little trickier. - fileAttributes &= (FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden); - File.SetAttributes(targetName, fileAttributes); - } - } - catch (Exception ex) - { - if (events_ != null) - { - continueRunning_ = events_.OnFileFailure(targetName, ex); - } - else - { - continueRunning_ = false; - throw; - } - } - } - } - } - - private void ExtractEntry(ZipEntry entry) - { - bool doExtraction = entry.IsCompressionMethodSupported(); - string targetName = entry.Name; - - if (doExtraction) - { - if (entry.IsFile) - { - targetName = extractNameTransform_.TransformFile(targetName); - } - else if (entry.IsDirectory) - { - targetName = extractNameTransform_.TransformDirectory(targetName); - } - - doExtraction = !(string.IsNullOrEmpty(targetName)); - } - - // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? - - string dirName = string.Empty; - - if (doExtraction) - { - if (entry.IsDirectory) - { - dirName = targetName; - } - else - { - dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); - } - } - - if (doExtraction && !Directory.Exists(dirName)) - { - if (!entry.IsDirectory || CreateEmptyDirectories) - { - try - { - continueRunning_ = events_?.OnProcessDirectory(dirName, true) ?? true; - if (continueRunning_) - { - Directory.CreateDirectory(dirName); - if (entry.IsDirectory && restoreDateTimeOnExtract_) - { - switch (entryFactory_.Setting) - { - case TimeSetting.CreateTime: - Directory.SetCreationTime(dirName, entry.DateTime); - break; - - case TimeSetting.CreateTimeUtc: - Directory.SetCreationTimeUtc(dirName, entry.DateTime); - break; - - case TimeSetting.LastAccessTime: - Directory.SetLastAccessTime(dirName, entry.DateTime); - break; - - case TimeSetting.LastAccessTimeUtc: - Directory.SetLastAccessTimeUtc(dirName, entry.DateTime); - break; - - case TimeSetting.LastWriteTime: - Directory.SetLastWriteTime(dirName, entry.DateTime); - break; - - case TimeSetting.LastWriteTimeUtc: - Directory.SetLastWriteTimeUtc(dirName, entry.DateTime); - break; - - case TimeSetting.Fixed: - Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime); - break; - - default: - throw new ZipException("Unhandled time setting in ExtractEntry"); - } - } - } - else - { - doExtraction = false; - } - } - catch (Exception ex) - { - doExtraction = false; - if (events_ != null) - { - if (entry.IsDirectory) - { - continueRunning_ = events_.OnDirectoryFailure(targetName, ex); - } - else - { - continueRunning_ = events_.OnFileFailure(targetName, ex); - } - } - else - { - continueRunning_ = false; - throw; - } - } - } - } - - if (doExtraction && entry.IsFile) - { - ExtractFileEntry(entry, targetName); - } - } - - private static int MakeExternalAttributes(FileInfo info) - { - return (int)info.Attributes; - } - - private static bool NameIsValid(string name) - { - return !string.IsNullOrEmpty(name) && - (name.IndexOfAny(Path.GetInvalidPathChars()) < 0); - } - - #endregion Internal Processing - - #region Instance Fields - - private bool continueRunning_; - private byte[] buffer_; - private ZipOutputStream outputStream_; - private ZipFile zipFile_; - private string sourceDirectory_; - private NameFilter fileFilter_; - private NameFilter directoryFilter_; - private Overwrite overwrite_; - private ConfirmOverwriteDelegate confirmDelegate_; - - private bool restoreDateTimeOnExtract_; - private bool restoreAttributesOnExtract_; - private bool createEmptyDirectories_; - private FastZipEvents events_; - private IEntryFactory entryFactory_ = new ZipEntryFactory(); - private INameTransform extractNameTransform_; - private UseZip64 useZip64_ = UseZip64.Dynamic; - private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION; - - private string password_; - - #endregion Instance Fields - } + #region Enumerations + + /// + /// Defines the desired handling when overwriting files during extraction. + /// + public enum Overwrite + { + /// + /// Prompt the user to confirm overwriting + /// + Prompt, + + /// + /// Never overwrite files. + /// + Never, + + /// + /// Always overwrite files. + /// + Always + } + + #endregion Enumerations + + #region Constructors + + /// + /// Initialise a default instance of . + /// + public FastZip() + { + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time setting to use when creating or extracting Zip entries. + /// Using TimeSetting.LastAccessTime[Utc] when + /// creating an archive will set the file time to the moment of reading. + /// + public FastZip(TimeSetting timeSetting) + { + entryFactory_ = new ZipEntryFactory(timeSetting); + restoreDateTimeOnExtract_ = true; + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time to set all values for created or extracted Zip Entries. + public FastZip(DateTime time) + { + entryFactory_ = new ZipEntryFactory(time); + restoreDateTimeOnExtract_ = true; + } + + /// + /// Initialise a new instance of + /// + /// The events to use during operations. + public FastZip(FastZipEvents events) + { + events_ = events; + } + + #endregion Constructors + + #region Properties + + /// + /// Get/set a value indicating whether empty directories should be created. + /// + public bool CreateEmptyDirectories + { + get { return createEmptyDirectories_; } + set { createEmptyDirectories_ = value; } + } + + /// + /// Get / set the password value. + /// + public string Password + { + get { return password_; } + set { password_ = value; } + } + + /// + /// Get / set the method of encrypting entries. + /// + /// + /// Only applies when is set. + /// Defaults to ZipCrypto for backwards compatibility purposes. + /// + public ZipEncryptionMethod EntryEncryptionMethod { get; set; } = ZipEncryptionMethod.ZipCrypto; + + /// + /// Get or set the active when creating Zip files. + /// + /// + public INameTransform NameTransform + { + get { return entryFactory_.NameTransform; } + set + { + entryFactory_.NameTransform = value; + } + } + + /// + /// Get or set the active when creating Zip files. + /// + public IEntryFactory EntryFactory + { + get { return entryFactory_; } + set + { + entryFactory_ = value == null ? new ZipEntryFactory() : value; + } + } + + /// + /// Gets or sets the setting for Zip64 handling when writing. + /// + /// + /// The default value is dynamic which is not backwards compatible with old + /// programs and can cause problems with XP's built in compression which cant + /// read Zip64 archives. However it does avoid the situation were a large file + /// is added and cannot be completed correctly. + /// NOTE: Setting the size for entries before they are added is the best solution! + /// By default the EntryFactory used by FastZip will set the file size. + /// + public UseZip64 UseZip64 + { + get { return useZip64_; } + set { useZip64_ = value; } + } + + /// + /// Get/set a value indicating whether file dates and times should + /// be restored when extracting files from an archive. + /// + /// The default value is false. + public bool RestoreDateTimeOnExtract + { + get + { + return restoreDateTimeOnExtract_; + } + set + { + restoreDateTimeOnExtract_ = value; + } + } + + /// + /// Get/set a value indicating whether file attributes should + /// be restored during extract operations + /// + public bool RestoreAttributesOnExtract + { + get { return restoreAttributesOnExtract_; } + set { restoreAttributesOnExtract_ = value; } + } + + /// + /// Get/set the Compression Level that will be used + /// when creating the zip + /// + public Deflater.CompressionLevel CompressionLevel + { + get { return compressionLevel_; } + set { compressionLevel_ = value; } + } + + #endregion Properties + + #region Delegates + + /// + /// Delegate called when confirming overwriting of files. + /// + public delegate bool ConfirmOverwriteDelegate(string fileName); + + #endregion Delegates + + #region CreateZip + + /// + /// Create a zip file. + /// + /// The name of the zip file to create. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + public void CreateZip(string zipFileName, string sourceDirectory, + bool recurse, string fileFilter, string directoryFilter) + { + CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter); + } + + /// + /// Create a zip file/archive. + /// + /// The name of the zip file to create. + /// The directory to obtain files and directories from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + public void CreateZip(string zipFileName, string sourceDirectory, bool recurse, string fileFilter) + { + CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, null); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + /// The is closed after creation. + public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter) + { + CreateZip(outputStream, sourceDirectory, recurse, fileFilter, directoryFilter, false); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + /// true to leave open after the zip has been created, false to dispose it. + public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, string fileFilter, string directoryFilter, bool leaveOpen) + { + var scanner = new FileSystemScanner(fileFilter, directoryFilter); + CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen); + } + + /// + /// Create a zip file. + /// + /// The name of the zip file to create. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + public void CreateZip(string zipFileName, string sourceDirectory, + bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter) + { + CreateZip(File.Create(zipFileName), sourceDirectory, recurse, fileFilter, directoryFilter, false); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// The file filter to apply. + /// The directory filter to apply. + /// true to leave open after the zip has been created, false to dispose it. + public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, IScanFilter fileFilter, IScanFilter directoryFilter, bool leaveOpen = false) + { + var scanner = new FileSystemScanner(fileFilter, directoryFilter); + CreateZip(outputStream, sourceDirectory, recurse, scanner, leaveOpen); + } + + /// + /// Create a zip archive sending output to the passed. + /// + /// The stream to write archive data to. + /// The directory to source files from. + /// True to recurse directories, false for no recursion. + /// For performing the actual file system scan + /// true to leave open after the zip has been created, false to dispose it. + /// The is closed after creation. + private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, FileSystemScanner scanner, bool leaveOpen) + { + NameTransform = new ZipNameTransform(sourceDirectory); + sourceDirectory_ = sourceDirectory; + + using (outputStream_ = new ZipOutputStream(outputStream)) + { + outputStream_.SetLevel((int)CompressionLevel); + outputStream_.IsStreamOwner = !leaveOpen; + outputStream_.NameTransform = null; // all required transforms handled by us + + if (false == string.IsNullOrEmpty(password_) && EntryEncryptionMethod != ZipEncryptionMethod.None) + { + outputStream_.Password = password_; + } + + outputStream_.UseZip64 = UseZip64; + scanner.ProcessFile += ProcessFile; + if (this.CreateEmptyDirectories) + { + scanner.ProcessDirectory += ProcessDirectory; + } + + if (events_ != null) + { + if (events_.FileFailure != null) + { + scanner.FileFailure += events_.FileFailure; + } + + if (events_.DirectoryFailure != null) + { + scanner.DirectoryFailure += events_.DirectoryFailure; + } + } + + scanner.Scan(sourceDirectory, recurse); + } + } + + #endregion CreateZip + + #region ExtractZip + + /// + /// Extract the contents of a zip file. + /// + /// The zip file to extract from. + /// The directory to save extracted information in. + /// A filter to apply to files. + public void ExtractZip(string zipFileName, string targetDirectory, string fileFilter) + { + ExtractZip(zipFileName, targetDirectory, Overwrite.Always, null, fileFilter, null, restoreDateTimeOnExtract_); + } + + /// + /// Extract the contents of a zip file. + /// + /// The zip file to extract from. + /// The directory to save extracted information in. + /// The style of overwriting to apply. + /// A delegate to invoke when confirming overwriting. + /// A filter to apply to files. + /// A filter to apply to directories. + /// Flag indicating whether to restore the date and time for extracted files. + /// Allow parent directory traversal in file paths (e.g. ../file) + public void ExtractZip(string zipFileName, string targetDirectory, + Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, + string fileFilter, string directoryFilter, bool restoreDateTime, bool allowParentTraversal = false) + { + Stream inputStream = File.Open(zipFileName, FileMode.Open, FileAccess.Read, FileShare.Read); + ExtractZip(inputStream, targetDirectory, overwrite, confirmDelegate, fileFilter, directoryFilter, restoreDateTime, true, allowParentTraversal); + } + + /// + /// Extract the contents of a zip file held in a stream. + /// + /// The seekable input stream containing the zip to extract from. + /// The directory to save extracted information in. + /// The style of overwriting to apply. + /// A delegate to invoke when confirming overwriting. + /// A filter to apply to files. + /// A filter to apply to directories. + /// Flag indicating whether to restore the date and time for extracted files. + /// Flag indicating whether the inputStream will be closed by this method. + /// Allow parent directory traversal in file paths (e.g. ../file) + public void ExtractZip(Stream inputStream, string targetDirectory, + Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate, + string fileFilter, string directoryFilter, bool restoreDateTime, + bool isStreamOwner, bool allowParentTraversal = false) + { + if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null)) + { + throw new ArgumentNullException(nameof(confirmDelegate)); + } + + continueRunning_ = true; + overwrite_ = overwrite; + confirmDelegate_ = confirmDelegate; + extractNameTransform_ = new WindowsNameTransform(targetDirectory, allowParentTraversal); + + fileFilter_ = new NameFilter(fileFilter); + directoryFilter_ = new NameFilter(directoryFilter); + restoreDateTimeOnExtract_ = restoreDateTime; + + using (zipFile_ = new ZipFile(inputStream, !isStreamOwner)) + { + if (password_ != null) + { + zipFile_.Password = password_; + } + + var enumerator = zipFile_.GetEnumerator(); + while (continueRunning_ && enumerator.MoveNext()) + { + var entry = (ZipEntry)enumerator.Current; + if (entry.IsFile) + { + // TODO Path.GetDirectory can fail here on invalid characters. + if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name)) + { + ExtractEntry(entry); + } + } + else if (entry.IsDirectory) + { + if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories) + { + ExtractEntry(entry); + } + } + else + { + // Do nothing for volume labels etc... + } + } + } + } + + #endregion ExtractZip + + #region Internal Processing + + private void ProcessDirectory(object sender, DirectoryEventArgs e) + { + if (!e.HasMatchingFiles && CreateEmptyDirectories) + { + events_?.OnProcessDirectory(e.Name, e.HasMatchingFiles); + + if (e.ContinueRunning) + { + if (e.Name != sourceDirectory_) + { + var entry = entryFactory_.MakeDirectoryEntry(e.Name); + outputStream_.PutNextEntry(entry); + } + } + } + } + + private void ProcessFile(object sender, ScanEventArgs e) + { + if ((events_ != null) && (events_.ProcessFile != null)) + { + events_.ProcessFile(sender, e); + } + + if (e.ContinueRunning) + { + try + { + // The open below is equivalent to OpenRead which guarantees that if opened the + // file will not be changed by subsequent openers, but precludes opening in some cases + // were it could succeed. ie the open may fail as its already open for writing and the share mode should reflect that. + using var stream = File.Open(e.Name, FileMode.Open, FileAccess.Read, FileShare.Read); + var entry = entryFactory_.MakeFileEntry(e.Name); + + // Set up AES encryption for the entry if required. + ConfigureEntryEncryption(entry); + + outputStream_.PutNextEntry(entry); + AddFileContents(e.Name, stream); + } + catch (Exception ex) + { + if (events_ != null) + { + continueRunning_ = events_.OnFileFailure(e.Name, ex); + } + else + { + continueRunning_ = false; + throw; + } + } + } + } + + // Set up the encryption method to use for the specific entry. + private void ConfigureEntryEncryption(ZipEntry entry) + { + // Only alter the entries options if AES isn't already enabled for it + // (it might have been set up by the entry factory, and if so we let that take precedence) + if (!string.IsNullOrEmpty(Password) && entry.AESEncryptionStrength == 0) + { + switch (EntryEncryptionMethod) + { + case ZipEncryptionMethod.AES128: + entry.AESKeySize = 128; + break; + + case ZipEncryptionMethod.AES256: + entry.AESKeySize = 256; + break; + } + } + } + + private void AddFileContents(string name, Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + buffer_ ??= new byte[4096]; + + if ((events_ != null) && (events_.Progress != null)) + { + StreamUtils.Copy(stream, outputStream_, buffer_, + events_.Progress, events_.ProgressInterval, this, name); + } + else + { + StreamUtils.Copy(stream, outputStream_, buffer_); + } + + if (events_ != null) + { + continueRunning_ = events_.OnCompletedFile(name); + } + } + + private void ExtractFileEntry(ZipEntry entry, string targetName) + { + var proceed = true; + if (overwrite_ != Overwrite.Always) + { + if (File.Exists(targetName)) + { + proceed = (overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null) && confirmDelegate_(targetName); + } + } + + if (proceed) + { + if (events_ != null) + { + continueRunning_ = events_.OnProcessFile(entry.Name); + } + + if (continueRunning_) + { + try + { + using (var outputStream = File.Create(targetName)) + { + buffer_ ??= new byte[4096]; + + using (var inputStream = zipFile_.GetInputStream(entry)) + { + if ((events_ != null) && (events_.Progress != null)) + { + StreamUtils.Copy(inputStream, outputStream, buffer_, + events_.Progress, events_.ProgressInterval, this, entry.Name, entry.Size); + } + else + { + StreamUtils.Copy(inputStream, outputStream, buffer_); + } + } + + if (events_ != null) + { + continueRunning_ = events_.OnCompletedFile(entry.Name); + } + } + + if (restoreDateTimeOnExtract_) + { + switch (entryFactory_.Setting) + { + case TimeSetting.CreateTime: + File.SetCreationTime(targetName, entry.DateTime); + break; + + case TimeSetting.CreateTimeUtc: + File.SetCreationTimeUtc(targetName, entry.DateTime); + break; + + case TimeSetting.LastAccessTime: + File.SetLastAccessTime(targetName, entry.DateTime); + break; + + case TimeSetting.LastAccessTimeUtc: + File.SetLastAccessTimeUtc(targetName, entry.DateTime); + break; + + case TimeSetting.LastWriteTime: + File.SetLastWriteTime(targetName, entry.DateTime); + break; + + case TimeSetting.LastWriteTimeUtc: + File.SetLastWriteTimeUtc(targetName, entry.DateTime); + break; + + case TimeSetting.Fixed: + File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime); + break; + + default: + throw new ZipException("Unhandled time setting in ExtractFileEntry"); + } + } + + if (RestoreAttributesOnExtract && entry.IsDOSEntry && (entry.ExternalFileAttributes != -1)) + { + var fileAttributes = (FileAttributes)entry.ExternalFileAttributes; + // TODO: FastZip - Setting of other file attributes on extraction is a little trickier. + fileAttributes &= FileAttributes.Archive | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.Hidden; + File.SetAttributes(targetName, fileAttributes); + } + } + catch (Exception ex) + { + if (events_ != null) + { + continueRunning_ = events_.OnFileFailure(targetName, ex); + } + else + { + continueRunning_ = false; + throw; + } + } + } + } + } + + private void ExtractEntry(ZipEntry entry) + { + var doExtraction = entry.IsCompressionMethodSupported(); + var targetName = entry.Name; + + if (doExtraction) + { + if (entry.IsFile) + { + targetName = extractNameTransform_.TransformFile(targetName); + } + else if (entry.IsDirectory) + { + targetName = extractNameTransform_.TransformDirectory(targetName); + } + + doExtraction = !string.IsNullOrEmpty(targetName); + } + + // TODO: Fire delegate/throw exception were compression method not supported, or name is invalid? + + var dirName = string.Empty; + + if (doExtraction) + { + dirName = entry.IsDirectory ? targetName : Path.GetDirectoryName(Path.GetFullPath(targetName)); + } + + if (doExtraction && !Directory.Exists(dirName)) + { + if (!entry.IsDirectory || CreateEmptyDirectories) + { + try + { + continueRunning_ = events_?.OnProcessDirectory(dirName, true) ?? true; + if (continueRunning_) + { + Directory.CreateDirectory(dirName); + if (entry.IsDirectory && restoreDateTimeOnExtract_) + { + switch (entryFactory_.Setting) + { + case TimeSetting.CreateTime: + Directory.SetCreationTime(dirName, entry.DateTime); + break; + + case TimeSetting.CreateTimeUtc: + Directory.SetCreationTimeUtc(dirName, entry.DateTime); + break; + + case TimeSetting.LastAccessTime: + Directory.SetLastAccessTime(dirName, entry.DateTime); + break; + + case TimeSetting.LastAccessTimeUtc: + Directory.SetLastAccessTimeUtc(dirName, entry.DateTime); + break; + + case TimeSetting.LastWriteTime: + Directory.SetLastWriteTime(dirName, entry.DateTime); + break; + + case TimeSetting.LastWriteTimeUtc: + Directory.SetLastWriteTimeUtc(dirName, entry.DateTime); + break; + + case TimeSetting.Fixed: + Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime); + break; + + default: + throw new ZipException("Unhandled time setting in ExtractEntry"); + } + } + } + else + { + doExtraction = false; + } + } + catch (Exception ex) + { + doExtraction = false; + if (events_ != null) + { + continueRunning_ = entry.IsDirectory ? events_.OnDirectoryFailure(targetName, ex) : events_.OnFileFailure(targetName, ex); + } + else + { + continueRunning_ = false; + throw; + } + } + } + } + + if (doExtraction && entry.IsFile) + { + ExtractFileEntry(entry, targetName); + } + } + + private static int MakeExternalAttributes(FileInfo info) + { + return (int)info.Attributes; + } + + private static bool NameIsValid(string name) + { + return !string.IsNullOrEmpty(name) && + (name.IndexOfAny(Path.GetInvalidPathChars()) < 0); + } + + #endregion Internal Processing + + #region Instance Fields + + private bool continueRunning_; + private byte[] buffer_; + private ZipOutputStream outputStream_; + private ZipFile zipFile_; + private string sourceDirectory_; + private NameFilter fileFilter_; + private NameFilter directoryFilter_; + private Overwrite overwrite_; + private ConfirmOverwriteDelegate confirmDelegate_; + + private bool restoreDateTimeOnExtract_; + private bool restoreAttributesOnExtract_; + private bool createEmptyDirectories_; + private readonly FastZipEvents events_; + private IEntryFactory entryFactory_ = new ZipEntryFactory(); + private INameTransform extractNameTransform_; + private UseZip64 useZip64_ = UseZip64.Dynamic; + private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION; + + private string password_; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs index 6bdbaa40e..04ca30c9e 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs @@ -1,67 +1,66 @@ -using System; using MelonLoader.ICSharpCode.SharpZipLib.Core; +using System; using static MelonLoader.ICSharpCode.SharpZipLib.Zip.ZipEntryFactory; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// Defines factory methods for creating new values. +/// +public interface IEntryFactory { - /// - /// Defines factory methods for creating new values. - /// - public interface IEntryFactory - { - /// - /// Create a for a file given its name - /// - /// The name of the file to create an entry for. - /// Returns a file entry based on the passed. - ZipEntry MakeFileEntry(string fileName); + /// + /// Create a for a file given its name + /// + /// The name of the file to create an entry for. + /// Returns a file entry based on the passed. + ZipEntry MakeFileEntry(string fileName); - /// - /// Create a for a file given its name - /// - /// The name of the file to create an entry for. - /// If true get details from the file system if the file exists. - /// Returns a file entry based on the passed. - ZipEntry MakeFileEntry(string fileName, bool useFileSystem); + /// + /// Create a for a file given its name + /// + /// The name of the file to create an entry for. + /// If true get details from the file system if the file exists. + /// Returns a file entry based on the passed. + ZipEntry MakeFileEntry(string fileName, bool useFileSystem); - /// - /// Create a for a file given its actual name and optional override name - /// - /// The name of the file to create an entry for. - /// An alternative name to be used for the new entry. Null if not applicable. - /// If true get details from the file system if the file exists. - /// Returns a file entry based on the passed. - ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem); + /// + /// Create a for a file given its actual name and optional override name + /// + /// The name of the file to create an entry for. + /// An alternative name to be used for the new entry. Null if not applicable. + /// If true get details from the file system if the file exists. + /// Returns a file entry based on the passed. + ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem); - /// - /// Create a for a directory given its name - /// - /// The name of the directory to create an entry for. - /// Returns a directory entry based on the passed. - ZipEntry MakeDirectoryEntry(string directoryName); + /// + /// Create a for a directory given its name + /// + /// The name of the directory to create an entry for. + /// Returns a directory entry based on the passed. + ZipEntry MakeDirectoryEntry(string directoryName); - /// - /// Create a for a directory given its name - /// - /// The name of the directory to create an entry for. - /// If true get details from the file system for this directory if it exists. - /// Returns a directory entry based on the passed. - ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem); + /// + /// Create a for a directory given its name + /// + /// The name of the directory to create an entry for. + /// If true get details from the file system for this directory if it exists. + /// Returns a directory entry based on the passed. + ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem); - /// - /// Get/set the applicable. - /// - INameTransform NameTransform { get; set; } + /// + /// Get/set the applicable. + /// + INameTransform NameTransform { get; set; } - /// - /// Get the in use. - /// - TimeSetting Setting { get; } + /// + /// Get the in use. + /// + TimeSetting Setting { get; } - /// - /// Get the value to use when is set to , - /// or if not specified, the value of when the class was the initialized - /// - DateTime FixedDateTime { get; } - } + /// + /// Get the value to use when is set to , + /// or if not specified, the value of when the class was the initialized + /// + DateTime FixedDateTime { get; } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs index e63b1a355..0d4aa6330 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs @@ -1,266 +1,252 @@ using MelonLoader.ICSharpCode.SharpZipLib.Core; using System; using System.IO; -using System.Runtime.InteropServices; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip -{ - /// - /// WindowsNameTransform transforms names to windows compatible ones. - /// - public class WindowsNameTransform : INameTransform - { - /// - /// The maximum windows path name permitted. - /// - /// This may not valid for all windows systems - CE?, etc but I cant find the equivalent in the CLR. - private const int MaxPath = 260; - - private string _baseDirectory; - private bool _trimIncomingPaths; - private char _replacementChar = '_'; - private bool _allowParentTraversal; +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; - /// - /// In this case we need Windows' invalid path characters. - /// Path.GetInvalidPathChars() only returns a subset invalid on all platforms. - /// - private static readonly char[] InvalidEntryChars = new char[] { - '"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', - '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', - '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', - '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', - '\u001e', '\u001f', +/// +/// WindowsNameTransform transforms names to windows compatible ones. +/// +public class WindowsNameTransform : INameTransform +{ + /// + /// The maximum windows path name permitted. + /// + /// This may not valid for all windows systems - CE?, etc but I cant find the equivalent in the CLR. + private const int MaxPath = 260; + + private string _baseDirectory; + private bool _trimIncomingPaths; + private char _replacementChar = '_'; + private bool _allowParentTraversal; + + /// + /// In this case we need Windows' invalid path characters. + /// Path.GetInvalidPathChars() only returns a subset invalid on all platforms. + /// + private static readonly char[] InvalidEntryChars = new char[] { + '"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', + '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', + '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', + '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', + '\u001e', '\u001f', // extra characters for masks, etc. '*', '?', ':' - }; - - /// - /// Initialises a new instance of - /// - /// - /// Allow parent directory traversal in file paths (e.g. ../file) - public WindowsNameTransform(string baseDirectory, bool allowParentTraversal = false) - { - BaseDirectory = baseDirectory ?? throw new ArgumentNullException(nameof(baseDirectory), "Directory name is invalid"); - AllowParentTraversal = allowParentTraversal; - } - - /// - /// Initialise a default instance of - /// - public WindowsNameTransform() - { - // Do nothing. - } - - /// - /// Gets or sets a value containing the target directory to prefix values with. - /// - public string BaseDirectory - { - get { return _baseDirectory; } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _baseDirectory = Path.GetFullPath(value); - } - } - - /// - /// Allow parent directory traversal in file paths (e.g. ../file) - /// - public bool AllowParentTraversal - { - get => _allowParentTraversal; - set => _allowParentTraversal = value; - } - - /// - /// Gets or sets a value indicating whether paths on incoming values should be removed. - /// - public bool TrimIncomingPaths - { - get { return _trimIncomingPaths; } - set { _trimIncomingPaths = value; } - } - - /// - /// Transform a Zip directory name to a windows directory name. - /// - /// The directory name to transform. - /// The transformed name. - public string TransformDirectory(string name) - { - name = TransformFile(name); - if (name.Length > 0) - { - while (name.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - name = name.Remove(name.Length - 1, 1); - } - } - else - { - throw new InvalidNameException("Cannot have an empty directory name"); - } - return name; - } - - /// - /// Transform a Zip format file name to a windows style one. - /// - /// The file name to transform. - /// The transformed name. - public string TransformFile(string name) - { - if (name != null) - { - name = MakeValidName(name, _replacementChar); - - if (_trimIncomingPaths) - { - name = Path.GetFileName(name); - } - - // This may exceed windows length restrictions. - // Combine will throw a PathTooLongException in that case. - if (_baseDirectory != null) - { - name = Path.Combine(_baseDirectory, name); - - // Ensure base directory ends with directory separator ('/' or '\' depending on OS) - var pathBase = Path.GetFullPath(_baseDirectory); - if (pathBase[pathBase.Length - 1] != Path.DirectorySeparatorChar) - { - pathBase += Path.DirectorySeparatorChar; - } - - if (!_allowParentTraversal && !Path.GetFullPath(name).StartsWith(pathBase, StringComparison.InvariantCultureIgnoreCase)) - { - throw new InvalidNameException("Parent traversal in paths is not allowed"); - } - } - } - else - { - name = string.Empty; - } - return name; - } - - /// - /// Test a name to see if it is a valid name for a windows filename as extracted from a Zip archive. - /// - /// The name to test. - /// Returns true if the name is a valid zip name; false otherwise. - /// The filename isnt a true windows path in some fundamental ways like no absolute paths, no rooted paths etc. - public static bool IsValidName(string name) - { - bool result = - (name != null) && - (name.Length <= MaxPath) && - (string.Compare(name, MakeValidName(name, '_'), StringComparison.Ordinal) == 0) - ; - - return result; - } - - /// - /// Force a name to be valid by replacing invalid characters with a fixed value - /// - /// The name to make valid - /// The replacement character to use for any invalid characters. - /// Returns a valid name - public static string MakeValidName(string name, char replacement) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - name = PathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString())); - - // Drop any leading slashes. - while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar)) - { - name = name.Remove(0, 1); - } - - // Drop any trailing slashes. - while ((name.Length > 0) && (name[name.Length - 1] == Path.DirectorySeparatorChar)) - { - name = name.Remove(name.Length - 1, 1); - } - - // Convert consecutive \\ characters to \ - int index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); - while (index >= 0) - { - name = name.Remove(index, 1); - index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); - } - - // Convert any invalid characters using the replacement one. - index = name.IndexOfAny(InvalidEntryChars); - if (index >= 0) - { - var builder = new StringBuilder(name); - - while (index >= 0) - { - builder[index] = replacement; - - if (index >= name.Length) - { - index = -1; - } - else - { - index = name.IndexOfAny(InvalidEntryChars, index + 1); - } - } - name = builder.ToString(); - } - - // Check for names greater than MaxPath characters. - // TODO: Were is CLR version of MaxPath defined? Can't find it in Environment. - if (name.Length > MaxPath) - { - throw new PathTooLongException(); - } - - return name; - } - - /// - /// Gets or set the character to replace invalid characters during transformations. - /// - public char Replacement - { - get { return _replacementChar; } - set - { - for (int i = 0; i < InvalidEntryChars.Length; ++i) - { - if (InvalidEntryChars[i] == value) - { - throw new ArgumentException("invalid path character"); - } - } - - if ((value == Path.DirectorySeparatorChar) || (value == Path.AltDirectorySeparatorChar)) - { - throw new ArgumentException("invalid replacement character"); - } - - _replacementChar = value; - } - } - } + }; + + /// + /// Initialises a new instance of + /// + /// + /// Allow parent directory traversal in file paths (e.g. ../file) + public WindowsNameTransform(string baseDirectory, bool allowParentTraversal = false) + { + BaseDirectory = baseDirectory ?? throw new ArgumentNullException(nameof(baseDirectory), "Directory name is invalid"); + AllowParentTraversal = allowParentTraversal; + } + + /// + /// Initialise a default instance of + /// + public WindowsNameTransform() + { + // Do nothing. + } + + /// + /// Gets or sets a value containing the target directory to prefix values with. + /// + public string BaseDirectory + { + get { return _baseDirectory; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _baseDirectory = Path.GetFullPath(value); + } + } + + /// + /// Allow parent directory traversal in file paths (e.g. ../file) + /// + public bool AllowParentTraversal + { + get => _allowParentTraversal; + set => _allowParentTraversal = value; + } + + /// + /// Gets or sets a value indicating whether paths on incoming values should be removed. + /// + public bool TrimIncomingPaths + { + get { return _trimIncomingPaths; } + set { _trimIncomingPaths = value; } + } + + /// + /// Transform a Zip directory name to a windows directory name. + /// + /// The directory name to transform. + /// The transformed name. + public string TransformDirectory(string name) + { + name = TransformFile(name); + if (name.Length > 0) + { + while (name.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + name = name.Remove(name.Length - 1, 1); + } + } + else + { + throw new InvalidNameException("Cannot have an empty directory name"); + } + return name; + } + + /// + /// Transform a Zip format file name to a windows style one. + /// + /// The file name to transform. + /// The transformed name. + public string TransformFile(string name) + { + if (name != null) + { + name = MakeValidName(name, _replacementChar); + + if (_trimIncomingPaths) + { + name = Path.GetFileName(name); + } + + // This may exceed windows length restrictions. + // Combine will throw a PathTooLongException in that case. + if (_baseDirectory != null) + { + name = Path.Combine(_baseDirectory, name); + + // Ensure base directory ends with directory separator ('/' or '\' depending on OS) + var pathBase = Path.GetFullPath(_baseDirectory); + if (pathBase[^1] != Path.DirectorySeparatorChar) + { + pathBase += Path.DirectorySeparatorChar; + } + + if (!_allowParentTraversal && !Path.GetFullPath(name).StartsWith(pathBase, StringComparison.InvariantCultureIgnoreCase)) + { + throw new InvalidNameException("Parent traversal in paths is not allowed"); + } + } + } + else + { + name = string.Empty; + } + return name; + } + + /// + /// Test a name to see if it is a valid name for a windows filename as extracted from a Zip archive. + /// + /// The name to test. + /// Returns true if the name is a valid zip name; false otherwise. + /// The filename isnt a true windows path in some fundamental ways like no absolute paths, no rooted paths etc. + public static bool IsValidName(string name) + { + var result = + (name != null) && + (name.Length <= MaxPath) && + (string.Compare(name, MakeValidName(name, '_'), StringComparison.Ordinal) == 0) + ; + + return result; + } + + /// + /// Force a name to be valid by replacing invalid characters with a fixed value + /// + /// The name to make valid + /// The replacement character to use for any invalid characters. + /// Returns a valid name + public static string MakeValidName(string name, char replacement) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + name = PathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString())); + + // Drop any leading slashes. + while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar)) + { + name = name.Remove(0, 1); + } + + // Drop any trailing slashes. + while ((name.Length > 0) && (name[^1] == Path.DirectorySeparatorChar)) + { + name = name.Remove(name.Length - 1, 1); + } + + // Convert consecutive \\ characters to \ + var index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); + while (index >= 0) + { + name = name.Remove(index, 1); + index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); + } + + // Convert any invalid characters using the replacement one. + index = name.IndexOfAny(InvalidEntryChars); + if (index >= 0) + { + var builder = new StringBuilder(name); + + while (index >= 0) + { + builder[index] = replacement; + + index = index >= name.Length ? -1 : name.IndexOfAny(InvalidEntryChars, index + 1); + } + name = builder.ToString(); + } + + // Check for names greater than MaxPath characters. + // TODO: Were is CLR version of MaxPath defined? Can't find it in Environment. + return name.Length > MaxPath ? throw new PathTooLongException() : name; + } + + /// + /// Gets or set the character to replace invalid characters during transformations. + /// + public char Replacement + { + get { return _replacementChar; } + set + { + for (var i = 0; i < InvalidEntryChars.Length; ++i) + { + if (InvalidEntryChars[i] == value) + { + throw new ArgumentException("invalid path character"); + } + } + + if ((value == Path.DirectorySeparatorChar) || (value == Path.AltDirectorySeparatorChar)) + { + throw new ArgumentException("invalid replacement character"); + } + + _replacementChar = value; + } + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs index 5f809ee8b..c890958d2 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs @@ -1,518 +1,517 @@ using System; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +#region Enumerations + +/// +/// Determines how entries are tested to see if they should use Zip64 extensions or not. +/// +public enum UseZip64 +{ + /// + /// Zip64 will not be forced on entries during processing. + /// + /// An entry can have this overridden if required + Off, + + /// + /// Zip64 should always be used. + /// + On, + + /// + /// #ZipLib will determine use based on entry values when added to archive. + /// + Dynamic, +} + +/// +/// The kind of compression used for an entry in an archive +/// +public enum CompressionMethod +{ + /// + /// A direct copy of the file contents is held in the archive + /// + Stored = 0, + + /// + /// Common Zip compression method using a sliding dictionary + /// of up to 32KB and secondary compression from Huffman/Shannon-Fano trees + /// + Deflated = 8, + + /// + /// An extension to deflate with a 64KB window. Not supported by #Zip currently + /// + Deflate64 = 9, + + /// + /// BZip2 compression. Not supported by #Zip. + /// + BZip2 = 12, + + /// + /// LZMA compression. Not supported by #Zip. + /// + LZMA = 14, + + /// + /// PPMd compression. Not supported by #Zip. + /// + PPMd = 98, + + /// + /// WinZip special for AES encryption, Now supported by #Zip. + /// + WinZipAES = 99, +} + +/// +/// Identifies the encryption algorithm used for an entry +/// +public enum EncryptionAlgorithm +{ + /// + /// No encryption has been used. + /// + None = 0, + + /// + /// Encrypted using PKZIP 2.0 or 'classic' encryption. + /// + PkzipClassic = 1, + + /// + /// DES encryption has been used. + /// + Des = 0x6601, + + /// + /// RC2 encryption has been used for encryption. + /// + RC2 = 0x6602, + + /// + /// Triple DES encryption with 168 bit keys has been used for this entry. + /// + TripleDes168 = 0x6603, + + /// + /// Triple DES with 112 bit keys has been used for this entry. + /// + TripleDes112 = 0x6609, + + /// + /// AES 128 has been used for encryption. + /// + Aes128 = 0x660e, + + /// + /// AES 192 has been used for encryption. + /// + Aes192 = 0x660f, + + /// + /// AES 256 has been used for encryption. + /// + Aes256 = 0x6610, + + /// + /// RC2 corrected has been used for encryption. + /// + RC2Corrected = 0x6702, + + /// + /// Blowfish has been used for encryption. + /// + Blowfish = 0x6720, + + /// + /// Twofish has been used for encryption. + /// + Twofish = 0x6721, + + /// + /// RC4 has been used for encryption. + /// + RC4 = 0x6801, + + /// + /// An unknown algorithm has been used for encryption. + /// + Unknown = 0xffff +} + +/// +/// Defines the contents of the general bit flags field for an archive entry. +/// +[Flags] +public enum GeneralBitFlags +{ + /// + /// Bit 0 if set indicates that the file is encrypted + /// + Encrypted = 0x0001, + + /// + /// Bits 1 and 2 - Two bits defining the compression method (only for Method 6 Imploding and 8,9 Deflating) + /// + Method = 0x0006, + + /// + /// Bit 3 if set indicates a trailing data descriptor is appended to the entry data + /// + Descriptor = 0x0008, + + /// + /// Bit 4 is reserved for use with method 8 for enhanced deflation + /// + ReservedPKware4 = 0x0010, + + /// + /// Bit 5 if set indicates the file contains Pkzip compressed patched data. + /// Requires version 2.7 or greater. + /// + Patched = 0x0020, + + /// + /// Bit 6 if set indicates strong encryption has been used for this entry. + /// + StrongEncryption = 0x0040, + + /// + /// Bit 7 is currently unused + /// + Unused7 = 0x0080, + + /// + /// Bit 8 is currently unused + /// + Unused8 = 0x0100, + + /// + /// Bit 9 is currently unused + /// + Unused9 = 0x0200, + + /// + /// Bit 10 is currently unused + /// + Unused10 = 0x0400, + + /// + /// Bit 11 if set indicates the filename and + /// comment fields for this file must be encoded using UTF-8. + /// + UnicodeText = 0x0800, + + /// + /// Bit 12 is documented as being reserved by PKware for enhanced compression. + /// + EnhancedCompress = 0x1000, + + /// + /// Bit 13 if set indicates that values in the local header are masked to hide + /// their actual values, and the central directory is encrypted. + /// + /// + /// Used when encrypting the central directory contents. + /// + HeaderMasked = 0x2000, + + /// + /// Bit 14 is documented as being reserved for use by PKware + /// + ReservedPkware14 = 0x4000, + + /// + /// Bit 15 is documented as being reserved for use by PKware + /// + ReservedPkware15 = 0x8000 +} + +#endregion Enumerations + +/// +/// This class contains constants used for Zip format files +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] +public static class ZipConstants { - #region Enumerations - - /// - /// Determines how entries are tested to see if they should use Zip64 extensions or not. - /// - public enum UseZip64 - { - /// - /// Zip64 will not be forced on entries during processing. - /// - /// An entry can have this overridden if required - Off, - - /// - /// Zip64 should always be used. - /// - On, - - /// - /// #ZipLib will determine use based on entry values when added to archive. - /// - Dynamic, - } - - /// - /// The kind of compression used for an entry in an archive - /// - public enum CompressionMethod - { - /// - /// A direct copy of the file contents is held in the archive - /// - Stored = 0, - - /// - /// Common Zip compression method using a sliding dictionary - /// of up to 32KB and secondary compression from Huffman/Shannon-Fano trees - /// - Deflated = 8, - - /// - /// An extension to deflate with a 64KB window. Not supported by #Zip currently - /// - Deflate64 = 9, - - /// - /// BZip2 compression. Not supported by #Zip. - /// - BZip2 = 12, - - /// - /// LZMA compression. Not supported by #Zip. - /// - LZMA = 14, - - /// - /// PPMd compression. Not supported by #Zip. - /// - PPMd = 98, - - /// - /// WinZip special for AES encryption, Now supported by #Zip. - /// - WinZipAES = 99, - } - - /// - /// Identifies the encryption algorithm used for an entry - /// - public enum EncryptionAlgorithm - { - /// - /// No encryption has been used. - /// - None = 0, - - /// - /// Encrypted using PKZIP 2.0 or 'classic' encryption. - /// - PkzipClassic = 1, - - /// - /// DES encryption has been used. - /// - Des = 0x6601, - - /// - /// RC2 encryption has been used for encryption. - /// - RC2 = 0x6602, - - /// - /// Triple DES encryption with 168 bit keys has been used for this entry. - /// - TripleDes168 = 0x6603, - - /// - /// Triple DES with 112 bit keys has been used for this entry. - /// - TripleDes112 = 0x6609, - - /// - /// AES 128 has been used for encryption. - /// - Aes128 = 0x660e, - - /// - /// AES 192 has been used for encryption. - /// - Aes192 = 0x660f, - - /// - /// AES 256 has been used for encryption. - /// - Aes256 = 0x6610, - - /// - /// RC2 corrected has been used for encryption. - /// - RC2Corrected = 0x6702, - - /// - /// Blowfish has been used for encryption. - /// - Blowfish = 0x6720, - - /// - /// Twofish has been used for encryption. - /// - Twofish = 0x6721, - - /// - /// RC4 has been used for encryption. - /// - RC4 = 0x6801, - - /// - /// An unknown algorithm has been used for encryption. - /// - Unknown = 0xffff - } - - /// - /// Defines the contents of the general bit flags field for an archive entry. - /// - [Flags] - public enum GeneralBitFlags - { - /// - /// Bit 0 if set indicates that the file is encrypted - /// - Encrypted = 0x0001, - - /// - /// Bits 1 and 2 - Two bits defining the compression method (only for Method 6 Imploding and 8,9 Deflating) - /// - Method = 0x0006, - - /// - /// Bit 3 if set indicates a trailing data descriptor is appended to the entry data - /// - Descriptor = 0x0008, - - /// - /// Bit 4 is reserved for use with method 8 for enhanced deflation - /// - ReservedPKware4 = 0x0010, - - /// - /// Bit 5 if set indicates the file contains Pkzip compressed patched data. - /// Requires version 2.7 or greater. - /// - Patched = 0x0020, - - /// - /// Bit 6 if set indicates strong encryption has been used for this entry. - /// - StrongEncryption = 0x0040, - - /// - /// Bit 7 is currently unused - /// - Unused7 = 0x0080, - - /// - /// Bit 8 is currently unused - /// - Unused8 = 0x0100, - - /// - /// Bit 9 is currently unused - /// - Unused9 = 0x0200, - - /// - /// Bit 10 is currently unused - /// - Unused10 = 0x0400, - - /// - /// Bit 11 if set indicates the filename and - /// comment fields for this file must be encoded using UTF-8. - /// - UnicodeText = 0x0800, - - /// - /// Bit 12 is documented as being reserved by PKware for enhanced compression. - /// - EnhancedCompress = 0x1000, - - /// - /// Bit 13 if set indicates that values in the local header are masked to hide - /// their actual values, and the central directory is encrypted. - /// - /// - /// Used when encrypting the central directory contents. - /// - HeaderMasked = 0x2000, - - /// - /// Bit 14 is documented as being reserved for use by PKware - /// - ReservedPkware14 = 0x4000, - - /// - /// Bit 15 is documented as being reserved for use by PKware - /// - ReservedPkware15 = 0x8000 - } - - #endregion Enumerations - - /// - /// This class contains constants used for Zip format files - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] - public static class ZipConstants - { - #region Versions - - /// - /// The version made by field for entries in the central header when created by this library - /// - /// - /// This is also the Zip version for the library when comparing against the version required to extract - /// for an entry. See . - /// - public const int VersionMadeBy = 51; // was 45 before AES - - /// - /// The version made by field for entries in the central header when created by this library - /// - /// - /// This is also the Zip version for the library when comparing against the version required to extract - /// for an entry. See ZipInputStream.CanDecompressEntry. - /// - [Obsolete("Use VersionMadeBy instead")] - public const int VERSION_MADE_BY = 51; - - /// - /// The minimum version required to support strong encryption - /// - public const int VersionStrongEncryption = 50; - - /// - /// The minimum version required to support strong encryption - /// - [Obsolete("Use VersionStrongEncryption instead")] - public const int VERSION_STRONG_ENCRYPTION = 50; - - /// - /// Version indicating AES encryption - /// - public const int VERSION_AES = 51; - - /// - /// The version required for Zip64 extensions (4.5 or higher) - /// - public const int VersionZip64 = 45; - - /// - /// The version required for BZip2 compression (4.6 or higher) - /// - public const int VersionBZip2 = 46; - - #endregion Versions - - #region Header Sizes - - /// - /// Size of local entry header (excluding variable length fields at end) - /// - public const int LocalHeaderBaseSize = 30; - - /// - /// Size of local entry header (excluding variable length fields at end) - /// - [Obsolete("Use LocalHeaderBaseSize instead")] - public const int LOCHDR = 30; - - /// - /// Size of Zip64 data descriptor - /// - public const int Zip64DataDescriptorSize = 24; - - /// - /// Size of data descriptor - /// - public const int DataDescriptorSize = 16; - - /// - /// Size of data descriptor - /// - [Obsolete("Use DataDescriptorSize instead")] - public const int EXTHDR = 16; - - /// - /// Size of central header entry (excluding variable fields) - /// - public const int CentralHeaderBaseSize = 46; - - /// - /// Size of central header entry - /// - [Obsolete("Use CentralHeaderBaseSize instead")] - public const int CENHDR = 46; - - /// - /// Size of end of central record (excluding variable fields) - /// - public const int EndOfCentralRecordBaseSize = 22; - - /// - /// Size of end of central record (excluding variable fields) - /// - [Obsolete("Use EndOfCentralRecordBaseSize instead")] - public const int ENDHDR = 22; - - /// - /// Size of 'classic' cryptographic header stored before any entry data - /// - public const int CryptoHeaderSize = 12; - - /// - /// Size of cryptographic header stored before entry data - /// - [Obsolete("Use CryptoHeaderSize instead")] - public const int CRYPTO_HEADER_SIZE = 12; - - /// - /// The size of the Zip64 central directory locator. - /// - public const int Zip64EndOfCentralDirectoryLocatorSize = 20; - - #endregion Header Sizes - - #region Header Signatures - - /// - /// Signature for local entry header - /// - public const int LocalHeaderSignature = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); - - /// - /// Signature for local entry header - /// - [Obsolete("Use LocalHeaderSignature instead")] - public const int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); - - /// - /// Signature for spanning entry - /// - public const int SpanningSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); - - /// - /// Signature for spanning entry - /// - [Obsolete("Use SpanningSignature instead")] - public const int SPANNINGSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); - - /// - /// Signature for temporary spanning entry - /// - public const int SpanningTempSignature = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); - - /// - /// Signature for temporary spanning entry - /// - [Obsolete("Use SpanningTempSignature instead")] - public const int SPANTEMPSIG = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); - - /// - /// Signature for data descriptor - /// - /// - /// This is only used where the length, Crc, or compressed size isnt known when the - /// entry is created and the output stream doesnt support seeking. - /// The local entry cannot be 'patched' with the correct values in this case - /// so the values are recorded after the data prefixed by this header, as well as in the central directory. - /// - public const int DataDescriptorSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); - - /// - /// Signature for data descriptor - /// - /// - /// This is only used where the length, Crc, or compressed size isnt known when the - /// entry is created and the output stream doesnt support seeking. - /// The local entry cannot be 'patched' with the correct values in this case - /// so the values are recorded after the data prefixed by this header, as well as in the central directory. - /// - [Obsolete("Use DataDescriptorSignature instead")] - public const int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); - - /// - /// Signature for central header - /// - [Obsolete("Use CentralHeaderSignature instead")] - public const int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); - - /// - /// Signature for central header - /// - public const int CentralHeaderSignature = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); - - /// - /// Signature for Zip64 central file header - /// - public const int Zip64CentralFileHeaderSignature = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); - - /// - /// Signature for Zip64 central file header - /// - [Obsolete("Use Zip64CentralFileHeaderSignature instead")] - public const int CENSIG64 = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); - - /// - /// Signature for Zip64 central directory locator - /// - public const int Zip64CentralDirLocatorSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); - - /// - /// Signature for archive extra data signature (were headers are encrypted). - /// - public const int ArchiveExtraDataSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); - - /// - /// Central header digital signature - /// - public const int CentralHeaderDigitalSignature = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); - - /// - /// Central header digital signature - /// - [Obsolete("Use CentralHeaderDigitalSignaure instead")] - public const int CENDIGITALSIG = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); - - /// - /// End of central directory record signature - /// - public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); - - /// - /// End of central directory record signature - /// - [Obsolete("Use EndOfCentralDirectorySignature instead")] - public const int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); - - #endregion Header Signatures - - /// - /// Default encoding used for string conversion. 0 gives the default system OEM code page. - /// Using the default code page isnt the full solution necessarily - /// there are many variable factors, codepage 850 is often a good choice for - /// European users, however be careful about compatability. - /// - [Obsolete("Use ZipStrings instead")] - public static int DefaultCodePage - { - get => ZipStrings.CodePage; - set => ZipStrings.CodePage = value; - } - - /// Deprecated wrapper for - [Obsolete("Use ZipStrings.ConvertToString instead")] - public static string ConvertToString(byte[] data, int count) - => ZipStrings.ConvertToString(data, count); - - /// Deprecated wrapper for - [Obsolete("Use ZipStrings.ConvertToString instead")] - public static string ConvertToString(byte[] data) - => ZipStrings.ConvertToString(data); - - /// Deprecated wrapper for - [Obsolete("Use ZipStrings.ConvertToStringExt instead")] - public static string ConvertToStringExt(int flags, byte[] data, int count) - => ZipStrings.ConvertToStringExt(flags, data, count); - - /// Deprecated wrapper for - [Obsolete("Use ZipStrings.ConvertToStringExt instead")] - public static string ConvertToStringExt(int flags, byte[] data) - => ZipStrings.ConvertToStringExt(flags, data); - - /// Deprecated wrapper for - [Obsolete("Use ZipStrings.ConvertToArray instead")] - public static byte[] ConvertToArray(string str) - => ZipStrings.ConvertToArray(str); - - /// Deprecated wrapper for - [Obsolete("Use ZipStrings.ConvertToArray instead")] - public static byte[] ConvertToArray(int flags, string str) - => ZipStrings.ConvertToArray(flags, str); - } + #region Versions + + /// + /// The version made by field for entries in the central header when created by this library + /// + /// + /// This is also the Zip version for the library when comparing against the version required to extract + /// for an entry. See . + /// + public const int VersionMadeBy = 51; // was 45 before AES + + /// + /// The version made by field for entries in the central header when created by this library + /// + /// + /// This is also the Zip version for the library when comparing against the version required to extract + /// for an entry. See ZipInputStream.CanDecompressEntry. + /// + [Obsolete("Use VersionMadeBy instead")] + public const int VERSION_MADE_BY = 51; + + /// + /// The minimum version required to support strong encryption + /// + public const int VersionStrongEncryption = 50; + + /// + /// The minimum version required to support strong encryption + /// + [Obsolete("Use VersionStrongEncryption instead")] + public const int VERSION_STRONG_ENCRYPTION = 50; + + /// + /// Version indicating AES encryption + /// + public const int VERSION_AES = 51; + + /// + /// The version required for Zip64 extensions (4.5 or higher) + /// + public const int VersionZip64 = 45; + + /// + /// The version required for BZip2 compression (4.6 or higher) + /// + public const int VersionBZip2 = 46; + + #endregion Versions + + #region Header Sizes + + /// + /// Size of local entry header (excluding variable length fields at end) + /// + public const int LocalHeaderBaseSize = 30; + + /// + /// Size of local entry header (excluding variable length fields at end) + /// + [Obsolete("Use LocalHeaderBaseSize instead")] + public const int LOCHDR = 30; + + /// + /// Size of Zip64 data descriptor + /// + public const int Zip64DataDescriptorSize = 24; + + /// + /// Size of data descriptor + /// + public const int DataDescriptorSize = 16; + + /// + /// Size of data descriptor + /// + [Obsolete("Use DataDescriptorSize instead")] + public const int EXTHDR = 16; + + /// + /// Size of central header entry (excluding variable fields) + /// + public const int CentralHeaderBaseSize = 46; + + /// + /// Size of central header entry + /// + [Obsolete("Use CentralHeaderBaseSize instead")] + public const int CENHDR = 46; + + /// + /// Size of end of central record (excluding variable fields) + /// + public const int EndOfCentralRecordBaseSize = 22; + + /// + /// Size of end of central record (excluding variable fields) + /// + [Obsolete("Use EndOfCentralRecordBaseSize instead")] + public const int ENDHDR = 22; + + /// + /// Size of 'classic' cryptographic header stored before any entry data + /// + public const int CryptoHeaderSize = 12; + + /// + /// Size of cryptographic header stored before entry data + /// + [Obsolete("Use CryptoHeaderSize instead")] + public const int CRYPTO_HEADER_SIZE = 12; + + /// + /// The size of the Zip64 central directory locator. + /// + public const int Zip64EndOfCentralDirectoryLocatorSize = 20; + + #endregion Header Sizes + + #region Header Signatures + + /// + /// Signature for local entry header + /// + public const int LocalHeaderSignature = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); + + /// + /// Signature for local entry header + /// + [Obsolete("Use LocalHeaderSignature instead")] + public const int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24); + + /// + /// Signature for spanning entry + /// + public const int SpanningSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for spanning entry + /// + [Obsolete("Use SpanningSignature instead")] + public const int SPANNINGSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for temporary spanning entry + /// + public const int SpanningTempSignature = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); + + /// + /// Signature for temporary spanning entry + /// + [Obsolete("Use SpanningTempSignature instead")] + public const int SPANTEMPSIG = 'P' | ('K' << 8) | ('0' << 16) | ('0' << 24); + + /// + /// Signature for data descriptor + /// + /// + /// This is only used where the length, Crc, or compressed size isnt known when the + /// entry is created and the output stream doesnt support seeking. + /// The local entry cannot be 'patched' with the correct values in this case + /// so the values are recorded after the data prefixed by this header, as well as in the central directory. + /// + public const int DataDescriptorSignature = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for data descriptor + /// + /// + /// This is only used where the length, Crc, or compressed size isnt known when the + /// entry is created and the output stream doesnt support seeking. + /// The local entry cannot be 'patched' with the correct values in this case + /// so the values are recorded after the data prefixed by this header, as well as in the central directory. + /// + [Obsolete("Use DataDescriptorSignature instead")] + public const int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24); + + /// + /// Signature for central header + /// + [Obsolete("Use CentralHeaderSignature instead")] + public const int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); + + /// + /// Signature for central header + /// + public const int CentralHeaderSignature = 'P' | ('K' << 8) | (1 << 16) | (2 << 24); + + /// + /// Signature for Zip64 central file header + /// + public const int Zip64CentralFileHeaderSignature = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); + + /// + /// Signature for Zip64 central file header + /// + [Obsolete("Use Zip64CentralFileHeaderSignature instead")] + public const int CENSIG64 = 'P' | ('K' << 8) | (6 << 16) | (6 << 24); + + /// + /// Signature for Zip64 central directory locator + /// + public const int Zip64CentralDirLocatorSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); + + /// + /// Signature for archive extra data signature (were headers are encrypted). + /// + public const int ArchiveExtraDataSignature = 'P' | ('K' << 8) | (6 << 16) | (7 << 24); + + /// + /// Central header digital signature + /// + public const int CentralHeaderDigitalSignature = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); + + /// + /// Central header digital signature + /// + [Obsolete("Use CentralHeaderDigitalSignaure instead")] + public const int CENDIGITALSIG = 'P' | ('K' << 8) | (5 << 16) | (5 << 24); + + /// + /// End of central directory record signature + /// + public const int EndOfCentralDirectorySignature = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); + + /// + /// End of central directory record signature + /// + [Obsolete("Use EndOfCentralDirectorySignature instead")] + public const int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24); + + #endregion Header Signatures + + /// + /// Default encoding used for string conversion. 0 gives the default system OEM code page. + /// Using the default code page isnt the full solution necessarily + /// there are many variable factors, codepage 850 is often a good choice for + /// European users, however be careful about compatability. + /// + [Obsolete("Use ZipStrings instead")] + public static int DefaultCodePage + { + get => ZipStrings.CodePage; + set => ZipStrings.CodePage = value; + } + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToString instead")] + public static string ConvertToString(byte[] data, int count) + => ZipStrings.ConvertToString(data, count); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToString instead")] + public static string ConvertToString(byte[] data) + => ZipStrings.ConvertToString(data); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToStringExt instead")] + public static string ConvertToStringExt(int flags, byte[] data, int count) + => ZipStrings.ConvertToStringExt(flags, data, count); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToStringExt instead")] + public static string ConvertToStringExt(int flags, byte[] data) + => ZipStrings.ConvertToStringExt(flags, data); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToArray instead")] + public static byte[] ConvertToArray(string str) + => ZipStrings.ConvertToArray(str); + + /// Deprecated wrapper for + [Obsolete("Use ZipStrings.ConvertToArray instead")] + public static byte[] ConvertToArray(int flags, string str) + => ZipStrings.ConvertToArray(flags, str); } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs index 4f5059266..652e6916d 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs @@ -1,28 +1,27 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// The method of encrypting entries when creating zip archives. +/// +public enum ZipEncryptionMethod { - /// - /// The method of encrypting entries when creating zip archives. - /// - public enum ZipEncryptionMethod - { - /// - /// No encryption will be used. - /// - None, + /// + /// No encryption will be used. + /// + None, - /// - /// Encrypt entries with ZipCrypto. - /// - ZipCrypto, + /// + /// Encrypt entries with ZipCrypto. + /// + ZipCrypto, - /// - /// Encrypt entries with AES 128. - /// - AES128, + /// + /// Encrypt entries with AES 128. + /// + AES128, - /// - /// Encrypt entries with AES 256. - /// - AES256 - } + /// + /// Encrypt entries with AES 256. + /// + AES256 } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs index a9368ba9b..b93882bd5 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs @@ -1,1155 +1,1131 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// Defines known values for the property. +/// +public enum HostSystemID +{ + /// + /// Host system = MSDOS + /// + Msdos = 0, + + /// + /// Host system = Amiga + /// + Amiga = 1, + + /// + /// Host system = Open VMS + /// + OpenVms = 2, + + /// + /// Host system = Unix + /// + Unix = 3, + + /// + /// Host system = VMCms + /// + VMCms = 4, + + /// + /// Host system = Atari ST + /// + AtariST = 5, + + /// + /// Host system = OS2 + /// + OS2 = 6, + + /// + /// Host system = Macintosh + /// + Macintosh = 7, + + /// + /// Host system = ZSystem + /// + ZSystem = 8, + + /// + /// Host system = Cpm + /// + Cpm = 9, + + /// + /// Host system = Windows NT + /// + WindowsNT = 10, + + /// + /// Host system = MVS + /// + MVS = 11, + + /// + /// Host system = VSE + /// + Vse = 12, + + /// + /// Host system = Acorn RISC + /// + AcornRisc = 13, + + /// + /// Host system = VFAT + /// + Vfat = 14, + + /// + /// Host system = Alternate MVS + /// + AlternateMvs = 15, + + /// + /// Host system = BEOS + /// + BeOS = 16, + + /// + /// Host system = Tandem + /// + Tandem = 17, + + /// + /// Host system = OS400 + /// + OS400 = 18, + + /// + /// Host system = OSX + /// + OSX = 19, + + /// + /// Host system = WinZIP AES + /// + WinZipAES = 99, +} + +/// +/// This class represents an entry in a zip archive. This can be a file +/// or a directory +/// ZipFile and ZipInputStream will give you instances of this class as +/// information about the members in an archive. ZipOutputStream +/// uses an instance of this class when creating an entry in a Zip file. +///
+///
Author of the original java version : Jochen Hoenicke +///
+public class ZipEntry { - /// - /// Defines known values for the property. - /// - public enum HostSystemID - { - /// - /// Host system = MSDOS - /// - Msdos = 0, - - /// - /// Host system = Amiga - /// - Amiga = 1, - - /// - /// Host system = Open VMS - /// - OpenVms = 2, - - /// - /// Host system = Unix - /// - Unix = 3, - - /// - /// Host system = VMCms - /// - VMCms = 4, - - /// - /// Host system = Atari ST - /// - AtariST = 5, - - /// - /// Host system = OS2 - /// - OS2 = 6, - - /// - /// Host system = Macintosh - /// - Macintosh = 7, - - /// - /// Host system = ZSystem - /// - ZSystem = 8, - - /// - /// Host system = Cpm - /// - Cpm = 9, - - /// - /// Host system = Windows NT - /// - WindowsNT = 10, - - /// - /// Host system = MVS - /// - MVS = 11, - - /// - /// Host system = VSE - /// - Vse = 12, - - /// - /// Host system = Acorn RISC - /// - AcornRisc = 13, - - /// - /// Host system = VFAT - /// - Vfat = 14, - - /// - /// Host system = Alternate MVS - /// - AlternateMvs = 15, - - /// - /// Host system = BEOS - /// - BeOS = 16, - - /// - /// Host system = Tandem - /// - Tandem = 17, - - /// - /// Host system = OS400 - /// - OS400 = 18, - - /// - /// Host system = OSX - /// - OSX = 19, - - /// - /// Host system = WinZIP AES - /// - WinZipAES = 99, - } - - /// - /// This class represents an entry in a zip archive. This can be a file - /// or a directory - /// ZipFile and ZipInputStream will give you instances of this class as - /// information about the members in an archive. ZipOutputStream - /// uses an instance of this class when creating an entry in a Zip file. - ///
- ///
Author of the original java version : Jochen Hoenicke - ///
- public class ZipEntry - { - [Flags] - private enum Known : byte - { - None = 0, - Size = 0x01, - CompressedSize = 0x02, - Crc = 0x04, - Time = 0x08, - ExternalAttributes = 0x10, - } - - #region Constructors - - /// - /// Creates a zip entry with the given name. - /// - /// - /// The name for this entry. Can include directory components. - /// The convention for names is 'unix' style paths with relative names only. - /// There are with no device names and path elements are separated by '/' characters. - /// - /// - /// The name passed is null - /// - public ZipEntry(string name) - : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated) - { - } - - /// - /// Creates a zip entry with the given name and version required to extract - /// - /// - /// The name for this entry. Can include directory components. - /// The convention for names is 'unix' style paths with no device names and - /// path elements separated by '/' characters. This is not enforced see CleanName - /// on how to ensure names are valid if this is desired. - /// - /// - /// The minimum 'feature version' required this entry - /// - /// - /// The name passed is null - /// - internal ZipEntry(string name, int versionRequiredToExtract) - : this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, - CompressionMethod.Deflated) - { - } - - /// - /// Initializes an entry with the given name and made by information - /// - /// Name for this entry - /// Version and HostSystem Information - /// Minimum required zip feature version required to extract this entry - /// Compression method for this entry. - /// - /// The name passed is null - /// - /// - /// versionRequiredToExtract should be 0 (auto-calculate) or > 10 - /// - /// - /// This constructor is used by the ZipFile class when reading from the central header - /// It is not generally useful, use the constructor specifying the name only. - /// - internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, - CompressionMethod method) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (name.Length > 0xffff) - { - throw new ArgumentException("Name is too long", nameof(name)); - } - - if ((versionRequiredToExtract != 0) && (versionRequiredToExtract < 10)) - { - throw new ArgumentOutOfRangeException(nameof(versionRequiredToExtract)); - } - - this.DateTime = DateTime.Now; - this.name = name; - this.versionMadeBy = (ushort)madeByInfo; - this.versionToExtract = (ushort)versionRequiredToExtract; - this.method = method; - - IsUnicodeText = ZipStrings.UseUnicode; - } - - /// - /// Creates a deep copy of the given zip entry. - /// - /// - /// The entry to copy. - /// - [Obsolete("Use Clone instead")] - public ZipEntry(ZipEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - known = entry.known; - name = entry.name; - size = entry.size; - compressedSize = entry.compressedSize; - crc = entry.crc; - dateTime = entry.DateTime; - method = entry.method; - comment = entry.comment; - versionToExtract = entry.versionToExtract; - versionMadeBy = entry.versionMadeBy; - externalFileAttributes = entry.externalFileAttributes; - flags = entry.flags; - - zipFileIndex = entry.zipFileIndex; - offset = entry.offset; - - forceZip64_ = entry.forceZip64_; - - if (entry.extra != null) - { - extra = new byte[entry.extra.Length]; - Array.Copy(entry.extra, 0, extra, 0, entry.extra.Length); - } - } - - #endregion Constructors - - /// - /// Get a value indicating whether the entry has a CRC value available. - /// - public bool HasCrc => (known & Known.Crc) != 0; - - /// - /// Get/Set flag indicating if entry is encrypted. - /// A simple helper routine to aid interpretation of flags - /// - /// This is an assistant that interprets the flags property. - public bool IsCrypted - { - get => this.HasFlag(GeneralBitFlags.Encrypted); - set => this.SetFlag(GeneralBitFlags.Encrypted, value); - } - - /// - /// Get / set a flag indicating whether entry name and comment text are - /// encoded in unicode UTF8. - /// - /// This is an assistant that interprets the flags property. - public bool IsUnicodeText - { - get => this.HasFlag(GeneralBitFlags.UnicodeText); - set => this.SetFlag(GeneralBitFlags.UnicodeText, value); - } - - /// - /// Value used during password checking for PKZIP 2.0 / 'classic' encryption. - /// - internal byte CryptoCheckValue - { - get => cryptoCheckValue_; - set => cryptoCheckValue_ = value; - } - - /// - /// Get/Set general purpose bit flag for entry - /// - /// - /// General purpose bit flag
- ///
- /// Bit 0: If set, indicates the file is encrypted
- /// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating
- /// Imploding:
- /// Bit 1 if set indicates an 8K sliding dictionary was used. If clear a 4k dictionary was used
- /// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise
- ///
- /// Deflating:
- /// Bit 2 Bit 1
- /// 0 0 Normal compression was used
- /// 0 1 Maximum compression was used
- /// 1 0 Fast compression was used
- /// 1 1 Super fast compression was used
- ///
- /// Bit 3: If set, the fields crc-32, compressed size - /// and uncompressed size are were not able to be written during zip file creation - /// The correct values are held in a data descriptor immediately following the compressed data.
- /// Bit 4: Reserved for use by PKZIP for enhanced deflating
- /// Bit 5: If set indicates the file contains compressed patch data
- /// Bit 6: If set indicates strong encryption was used.
- /// Bit 7-10: Unused or reserved
- /// Bit 11: If set the name and comments for this entry are in unicode.
- /// Bit 12-15: Unused or reserved
- ///
- /// - /// - public int Flags - { - get => flags; - set => flags = value; - } - - /// - /// Get/Set index of this entry in Zip file - /// - /// This is only valid when the entry is part of a - public long ZipFileIndex - { - get => zipFileIndex; - set => zipFileIndex = value; - } - - /// - /// Get/set offset for use in central header - /// - public long Offset - { - get => offset; - set => offset = value; - } - - /// - /// Get/Set external file attributes as an integer. - /// The values of this are operating system dependent see - /// HostSystem for details - /// - public int ExternalFileAttributes - { - get => (known & Known.ExternalAttributes) == 0 ? -1 : externalFileAttributes; - - set - { - externalFileAttributes = value; - known |= Known.ExternalAttributes; - } - } - - /// - /// Get the version made by for this entry or zero if unknown. - /// The value / 10 indicates the major version number, and - /// the value mod 10 is the minor version number - /// - public int VersionMadeBy => versionMadeBy & 0xff; - - /// - /// Get a value indicating this entry is for a DOS/Windows system. - /// - public bool IsDOSEntry - => (HostSystem == (int)HostSystemID.Msdos) - || (HostSystem == (int)HostSystemID.WindowsNT); - - /// - /// Test the external attributes for this to - /// see if the external attributes are Dos based (including WINNT and variants) - /// and match the values - /// - /// The attributes to test. - /// Returns true if the external attributes are known to be DOS/Windows - /// based and have the same attributes set as the value passed. - private bool HasDosAttributes(int attributes) - { - bool result = false; - if ((known & Known.ExternalAttributes) != 0) - { - result |= (((HostSystem == (int)HostSystemID.Msdos) || - (HostSystem == (int)HostSystemID.WindowsNT)) && - (ExternalFileAttributes & attributes) == attributes); - } - return result; - } - - /// - /// Gets the compatibility information for the external file attribute - /// If the external file attributes are compatible with MS-DOS and can be read - /// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value - /// will be non-zero and identify the host system on which the attributes are compatible. - /// - /// - /// - /// The values for this as defined in the Zip File format and by others are shown below. The values are somewhat - /// misleading in some cases as they are not all used as shown. You should consult the relevant documentation - /// to obtain up to date and correct information. The modified appnote by the infozip group is - /// particularly helpful as it documents a lot of peculiarities. The document is however a little dated. - /// - /// 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems) - /// 1 - Amiga - /// 2 - OpenVMS - /// 3 - Unix - /// 4 - VM/CMS - /// 5 - Atari ST - /// 6 - OS/2 HPFS - /// 7 - Macintosh - /// 8 - Z-System - /// 9 - CP/M - /// 10 - Windows NTFS - /// 11 - MVS (OS/390 - Z/OS) - /// 12 - VSE - /// 13 - Acorn Risc - /// 14 - VFAT - /// 15 - Alternate MVS - /// 16 - BeOS - /// 17 - Tandem - /// 18 - OS/400 - /// 19 - OS/X (Darwin) - /// 99 - WinZip AES - /// remainder - unused - /// - /// - public int HostSystem - { - get => (versionMadeBy >> 8) & 0xff; - - set - { - versionMadeBy &= 0x00ff; - versionMadeBy |= (ushort)((value & 0xff) << 8); - } - } - - /// - /// Get minimum Zip feature version required to extract this entry - /// - /// - /// Minimum features are defined as:
- /// 1.0 - Default value
- /// 1.1 - File is a volume label
- /// 2.0 - File is a folder/directory
- /// 2.0 - File is compressed using Deflate compression
- /// 2.0 - File is encrypted using traditional encryption
- /// 2.1 - File is compressed using Deflate64
- /// 2.5 - File is compressed using PKWARE DCL Implode
- /// 2.7 - File is a patch data set
- /// 4.5 - File uses Zip64 format extensions
- /// 4.6 - File is compressed using BZIP2 compression
- /// 5.0 - File is encrypted using DES
- /// 5.0 - File is encrypted using 3DES
- /// 5.0 - File is encrypted using original RC2 encryption
- /// 5.0 - File is encrypted using RC4 encryption
- /// 5.1 - File is encrypted using AES encryption
- /// 5.1 - File is encrypted using corrected RC2 encryption
- /// 5.1 - File is encrypted using corrected RC2-64 encryption
- /// 6.1 - File is encrypted using non-OAEP key wrapping
- /// 6.2 - Central directory encryption (not confirmed yet)
- /// 6.3 - File is compressed using LZMA
- /// 6.3 - File is compressed using PPMD+
- /// 6.3 - File is encrypted using Blowfish
- /// 6.3 - File is encrypted using Twofish
- ///
- /// - public int Version - { - get - { - // Return recorded version if known. - if (versionToExtract != 0) - // Only lower order byte. High order is O/S file system. - return versionToExtract & 0x00ff; - - if (AESKeySize > 0) - // Ver 5.1 = AES - return ZipConstants.VERSION_AES; - - if (CompressionMethod.BZip2 == method) - return ZipConstants.VersionBZip2; - - if (CentralHeaderRequiresZip64) - return ZipConstants.VersionZip64; - - if (CompressionMethod.Deflated == method || IsDirectory || IsCrypted) - return 20; - - if (HasDosAttributes(0x08)) - return 11; - - return 10; - } - } - - /// - /// Get a value indicating whether this entry can be decompressed by the library. - /// - /// This is based on the and - /// whether the compression method is supported. - public bool CanDecompress - => Version <= ZipConstants.VersionMadeBy - && (Version == 10 || Version == 11 || Version == 20 || Version == 45 || Version == 46 || Version == 51) - && IsCompressionMethodSupported(); - - /// - /// Force this entry to be recorded using Zip64 extensions. - /// - public void ForceZip64() => forceZip64_ = true; - - /// - /// Get a value indicating whether Zip64 extensions were forced. - /// - /// A value of true if Zip64 extensions have been forced on; false if not. - public bool IsZip64Forced() => forceZip64_; - - /// - /// Gets a value indicating if the entry requires Zip64 extensions - /// to store the full entry values. - /// - /// A value of true if a local header requires Zip64 extensions; false if not. - public bool LocalHeaderRequiresZip64 - { - get - { - bool result = forceZip64_; - - if (!result) - { - ulong trueCompressedSize = compressedSize; - - if ((versionToExtract == 0) && IsCrypted) - { - trueCompressedSize += (ulong)this.EncryptionOverheadSize; - } - - // TODO: A better estimation of the true limit based on compression overhead should be used - // to determine when an entry should use Zip64. - result = - ((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) && - ((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64)); - } - - return result; - } - } - - /// - /// Get a value indicating whether the central directory entry requires Zip64 extensions to be stored. - /// - public bool CentralHeaderRequiresZip64 - => LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); - - /// - /// Get/Set DosTime value. - /// - /// - /// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107. - /// - public long DosTime - { - get - { - if ((known & Known.Time) == 0) - { - return 0; - } - - var year = (uint)DateTime.Year; - var month = (uint)DateTime.Month; - var day = (uint)DateTime.Day; - var hour = (uint)DateTime.Hour; - var minute = (uint)DateTime.Minute; - var second = (uint)DateTime.Second; - - if (year < 1980) - { - year = 1980; - month = 1; - day = 1; - hour = 0; - minute = 0; - second = 0; - } - else if (year > 2107) - { - year = 2107; - month = 12; - day = 31; - hour = 23; - minute = 59; - second = 59; - } - - return ((year - 1980) & 0x7f) << 25 | - (month << 21) | - (day << 16) | - (hour << 11) | - (minute << 5) | - (second >> 1); - } - - set - { - unchecked - { - var dosTime = (uint)value; - uint sec = Math.Min(59, 2 * (dosTime & 0x1f)); - uint min = Math.Min(59, (dosTime >> 5) & 0x3f); - uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f); - uint mon = Math.Max(1, Math.Min(12, ((uint)(value >> 21) & 0xf))); - uint year = ((dosTime >> 25) & 0x7f) + 1980; - int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((value >> 16) & 0x1f))); - DateTime = new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Unspecified); - } - } - } - - /// - /// Gets/Sets the time of last modification of the entry. - /// - /// - /// The property is updated to match this as far as possible. - /// - public DateTime DateTime - { - get => dateTime; - - set - { - dateTime = value; - known |= Known.Time; - } - } - - /// - /// Returns the entry name. - /// - /// - /// The unix naming convention is followed. - /// Path components in the entry should always separated by forward slashes ('/'). - /// Dos device names like C: should also be removed. - /// See the class, or - /// - public string Name - { - get => name; - internal set => name = value; - } - - /// - /// Gets/Sets the size of the uncompressed data. - /// - /// - /// The size or -1 if unknown. - /// - /// Setting the size before adding an entry to an archive can help - /// avoid compatibility problems with some archivers which don't understand Zip64 extensions. - public long Size - { - get => (known & Known.Size) != 0 ? (long)size : -1L; - set - { - size = (ulong)value; - known |= Known.Size; - } - } - - /// - /// Gets/Sets the size of the compressed data. - /// - /// - /// The compressed entry size or -1 if unknown. - /// - public long CompressedSize - { - get => (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; - set - { - compressedSize = (ulong)value; - known |= Known.CompressedSize; - } - } - - /// - /// Gets/Sets the crc of the uncompressed data. - /// - /// - /// Crc is not in the range 0..0xffffffffL - /// - /// - /// The crc value or -1 if unknown. - /// - public long Crc - { - get => (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; - set - { - if ((crc & 0xffffffff00000000L) != 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - this.crc = (uint)value; - this.known |= Known.Crc; - } - } - - /// - /// Gets/Sets the compression method. - /// - /// - /// The compression method for this entry - /// - public CompressionMethod CompressionMethod - { - get => method; - set => method = value; - } - - /// - /// Gets the compression method for outputting to the local or central header. - /// Returns same value as CompressionMethod except when AES encrypting, which - /// places 99 in the method and places the real method in the extra data. - /// - internal CompressionMethod CompressionMethodForHeader - => (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; - - /// - /// Gets/Sets the extra data. - /// - /// - /// Extra data is longer than 64KB (0xffff) bytes. - /// - /// - /// Extra data or null if not set. - /// - public byte[] ExtraData - { - // TODO: This is slightly safer but less efficient. Think about whether it should change. - // return (byte[]) extra.Clone(); - get => extra; - - set - { - if (value == null) - { - extra = null; - } - else - { - if (value.Length > 0xffff) - { - throw new System.ArgumentOutOfRangeException(nameof(value)); - } - - extra = new byte[value.Length]; - Array.Copy(value, 0, extra, 0, value.Length); - } - } - } - - /// - /// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256). - /// When setting, only 0 (off), 128 or 256 is supported. - /// - public int AESKeySize - { - get - { - // the strength (1 or 3) is in the entry header - switch (_aesEncryptionStrength) - { - case 0: - return 0; // Not AES - case 1: - return 128; - - case 2: - return 192; // Not used by WinZip - case 3: - return 256; - - default: - throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength); - } - } - set - { - switch (value) - { - case 0: - _aesEncryptionStrength = 0; - break; - - case 128: - _aesEncryptionStrength = 1; - break; - - case 256: - _aesEncryptionStrength = 3; - break; - - default: - throw new ZipException("AESKeySize must be 0, 128 or 256: " + value); - } - } - } - - /// - /// AES Encryption strength for storage in extra data in entry header. - /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. - /// - internal byte AESEncryptionStrength => (byte)_aesEncryptionStrength; - - /// - /// Returns the length of the salt, in bytes - /// - /// Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. - internal int AESSaltLen => AESKeySize / 16; - - /// - /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) - /// - /// File format: - /// Bytes | Content - /// ---------+--------------------------- - /// Variable | Salt value - /// 2 | Password verification value - /// Variable | Encrypted file data - /// 10 | Authentication code - internal int AESOverheadSize => 12 + AESSaltLen; - - /// - /// Number of extra bytes required to hold the encryption header fields. - /// - internal int EncryptionOverheadSize => - !IsCrypted - // Entry is not encrypted - no overhead - ? 0 - : _aesEncryptionStrength == 0 - // Entry is encrypted using ZipCrypto - ? ZipConstants.CryptoHeaderSize - // Entry is encrypted using AES - : AESOverheadSize; - - /// - /// Process extra data fields updating the entry based on the contents. - /// - /// True if the extra data fields should be handled - /// for a local header, rather than for a central header. - /// - internal void ProcessExtraData(bool localHeader) - { - var extraData = new ZipExtraData(this.extra); - - if (extraData.Find(0x0001)) - { - // Version required to extract is ignored here as some archivers dont set it correctly - // in theory it should be version 45 or higher - - // The recorded size will change but remember that this is zip64. - forceZip64_ = true; - - if (extraData.ValueLength < 4) - { - throw new ZipException("Extra data extended Zip64 information length is invalid"); - } - - // (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between local header & central directory - // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT - // ... - // 4.4 Explanation of fields - // ... - // 4.4.8 compressed size: (4 bytes) - // 4.4.9 uncompressed size: (4 bytes) - // - // The size of the file compressed (4.4.8) and uncompressed, - // (4.4.9) respectively. When a decryption header is present it - // will be placed in front of the file data and the value of the - // compressed file size will include the bytes of the decryption - // header. If bit 3 of the general purpose bit flag is set, - // these fields are set to zero in the local header and the - // correct values are put in the data descriptor and - // in the central directory. If an archive is in ZIP64 format - // and the value in this field is 0xFFFFFFFF, the size will be - // in the corresponding 8 byte ZIP64 extended information - // extra field. When encrypting the central directory, if the - // local header is not in ZIP64 format and general purpose bit - // flag 13 is set indicating masking, the value stored for the - // uncompressed size in the Local Header will be zero. - // - // Otherwise there is problem with minizip implementation - if (size == uint.MaxValue) - { - size = (ulong)extraData.ReadLong(); - } - - if (compressedSize == uint.MaxValue) - { - compressedSize = (ulong)extraData.ReadLong(); - } - - if (!localHeader && (offset == uint.MaxValue)) - { - offset = extraData.ReadLong(); - } - - // Disk number on which file starts is ignored - } - else - { - if ( - ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && - ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) - ) - { - throw new ZipException("Zip64 Extended information required but is missing."); - } - } - - DateTime = GetDateTime(extraData) ?? DateTime; - if (method == CompressionMethod.WinZipAES) - { - ProcessAESExtraData(extraData); - } - } - - private static DateTime? GetDateTime(ZipExtraData extraData) - { - // Check for NT timestamp - // NOTE: Disable by default to match behavior of InfoZIP + [Flags] + private enum Known : byte + { + None = 0, + Size = 0x01, + CompressedSize = 0x02, + Crc = 0x04, + Time = 0x08, + ExternalAttributes = 0x10, + } + + #region Constructors + + /// + /// Creates a zip entry with the given name. + /// + /// + /// The name for this entry. Can include directory components. + /// The convention for names is 'unix' style paths with relative names only. + /// There are with no device names and path elements are separated by '/' characters. + /// + /// + /// The name passed is null + /// + public ZipEntry(string name) + : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated) + { + } + + /// + /// Creates a zip entry with the given name and version required to extract + /// + /// + /// The name for this entry. Can include directory components. + /// The convention for names is 'unix' style paths with no device names and + /// path elements separated by '/' characters. This is not enforced see CleanName + /// on how to ensure names are valid if this is desired. + /// + /// + /// The minimum 'feature version' required this entry + /// + /// + /// The name passed is null + /// + internal ZipEntry(string name, int versionRequiredToExtract) + : this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, + CompressionMethod.Deflated) + { + } + + /// + /// Initializes an entry with the given name and made by information + /// + /// Name for this entry + /// Version and HostSystem Information + /// Minimum required zip feature version required to extract this entry + /// Compression method for this entry. + /// + /// The name passed is null + /// + /// + /// versionRequiredToExtract should be 0 (auto-calculate) or > 10 + /// + /// + /// This constructor is used by the ZipFile class when reading from the central header + /// It is not generally useful, use the constructor specifying the name only. + /// + internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, + CompressionMethod method) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (name.Length > 0xffff) + { + throw new ArgumentException("Name is too long", nameof(name)); + } + + if (versionRequiredToExtract is not 0 and < 10) + { + throw new ArgumentOutOfRangeException(nameof(versionRequiredToExtract)); + } + + this.DateTime = DateTime.Now; + this.name = name; + this.versionMadeBy = (ushort)madeByInfo; + this.versionToExtract = (ushort)versionRequiredToExtract; + this.method = method; + + IsUnicodeText = ZipStrings.UseUnicode; + } + + /// + /// Creates a deep copy of the given zip entry. + /// + /// + /// The entry to copy. + /// + [Obsolete("Use Clone instead")] + public ZipEntry(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + known = entry.known; + name = entry.name; + size = entry.size; + compressedSize = entry.compressedSize; + crc = entry.crc; + dateTime = entry.DateTime; + method = entry.method; + comment = entry.comment; + versionToExtract = entry.versionToExtract; + versionMadeBy = entry.versionMadeBy; + externalFileAttributes = entry.externalFileAttributes; + flags = entry.flags; + + zipFileIndex = entry.zipFileIndex; + offset = entry.offset; + + forceZip64_ = entry.forceZip64_; + + if (entry.extra != null) + { + extra = new byte[entry.extra.Length]; + Array.Copy(entry.extra, 0, extra, 0, entry.extra.Length); + } + } + + #endregion Constructors + + /// + /// Get a value indicating whether the entry has a CRC value available. + /// + public bool HasCrc => (known & Known.Crc) != 0; + + /// + /// Get/Set flag indicating if entry is encrypted. + /// A simple helper routine to aid interpretation of flags + /// + /// This is an assistant that interprets the flags property. + public bool IsCrypted + { + get => this.HasFlag(GeneralBitFlags.Encrypted); + set => this.SetFlag(GeneralBitFlags.Encrypted, value); + } + + /// + /// Get / set a flag indicating whether entry name and comment text are + /// encoded in unicode UTF8. + /// + /// This is an assistant that interprets the flags property. + public bool IsUnicodeText + { + get => this.HasFlag(GeneralBitFlags.UnicodeText); + set => this.SetFlag(GeneralBitFlags.UnicodeText, value); + } + + /// + /// Value used during password checking for PKZIP 2.0 / 'classic' encryption. + /// + internal byte CryptoCheckValue + { + get => cryptoCheckValue_; + set => cryptoCheckValue_ = value; + } + + /// + /// Get/Set general purpose bit flag for entry + /// + /// + /// General purpose bit flag
+ ///
+ /// Bit 0: If set, indicates the file is encrypted
+ /// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating
+ /// Imploding:
+ /// Bit 1 if set indicates an 8K sliding dictionary was used. If clear a 4k dictionary was used
+ /// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise
+ ///
+ /// Deflating:
+ /// Bit 2 Bit 1
+ /// 0 0 Normal compression was used
+ /// 0 1 Maximum compression was used
+ /// 1 0 Fast compression was used
+ /// 1 1 Super fast compression was used
+ ///
+ /// Bit 3: If set, the fields crc-32, compressed size + /// and uncompressed size are were not able to be written during zip file creation + /// The correct values are held in a data descriptor immediately following the compressed data.
+ /// Bit 4: Reserved for use by PKZIP for enhanced deflating
+ /// Bit 5: If set indicates the file contains compressed patch data
+ /// Bit 6: If set indicates strong encryption was used.
+ /// Bit 7-10: Unused or reserved
+ /// Bit 11: If set the name and comments for this entry are in unicode.
+ /// Bit 12-15: Unused or reserved
+ ///
+ /// + /// + public int Flags + { + get => flags; + set => flags = value; + } + + /// + /// Get/Set index of this entry in Zip file + /// + /// This is only valid when the entry is part of a + public long ZipFileIndex + { + get => zipFileIndex; + set => zipFileIndex = value; + } + + /// + /// Get/set offset for use in central header + /// + public long Offset + { + get => offset; + set => offset = value; + } + + /// + /// Get/Set external file attributes as an integer. + /// The values of this are operating system dependent see + /// HostSystem for details + /// + public int ExternalFileAttributes + { + get => (known & Known.ExternalAttributes) == 0 ? -1 : externalFileAttributes; + + set + { + externalFileAttributes = value; + known |= Known.ExternalAttributes; + } + } + + /// + /// Get the version made by for this entry or zero if unknown. + /// The value / 10 indicates the major version number, and + /// the value mod 10 is the minor version number + /// + public int VersionMadeBy => versionMadeBy & 0xff; + + /// + /// Get a value indicating this entry is for a DOS/Windows system. + /// + public bool IsDOSEntry + => HostSystem is ((int)HostSystemID.Msdos) or ((int)HostSystemID.WindowsNT); + + /// + /// Test the external attributes for this to + /// see if the external attributes are Dos based (including WINNT and variants) + /// and match the values + /// + /// The attributes to test. + /// Returns true if the external attributes are known to be DOS/Windows + /// based and have the same attributes set as the value passed. + private bool HasDosAttributes(int attributes) + { + var result = false; + if ((known & Known.ExternalAttributes) != 0) + { + result |= ((HostSystem == (int)HostSystemID.Msdos) || + (HostSystem == (int)HostSystemID.WindowsNT)) && + (ExternalFileAttributes & attributes) == attributes; + } + return result; + } + + /// + /// Gets the compatibility information for the external file attribute + /// If the external file attributes are compatible with MS-DOS and can be read + /// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value + /// will be non-zero and identify the host system on which the attributes are compatible. + /// + /// + /// + /// The values for this as defined in the Zip File format and by others are shown below. The values are somewhat + /// misleading in some cases as they are not all used as shown. You should consult the relevant documentation + /// to obtain up to date and correct information. The modified appnote by the infozip group is + /// particularly helpful as it documents a lot of peculiarities. The document is however a little dated. + /// + /// 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems) + /// 1 - Amiga + /// 2 - OpenVMS + /// 3 - Unix + /// 4 - VM/CMS + /// 5 - Atari ST + /// 6 - OS/2 HPFS + /// 7 - Macintosh + /// 8 - Z-System + /// 9 - CP/M + /// 10 - Windows NTFS + /// 11 - MVS (OS/390 - Z/OS) + /// 12 - VSE + /// 13 - Acorn Risc + /// 14 - VFAT + /// 15 - Alternate MVS + /// 16 - BeOS + /// 17 - Tandem + /// 18 - OS/400 + /// 19 - OS/X (Darwin) + /// 99 - WinZip AES + /// remainder - unused + /// + /// + public int HostSystem + { + get => (versionMadeBy >> 8) & 0xff; + + set + { + versionMadeBy &= 0x00ff; + versionMadeBy |= (ushort)((value & 0xff) << 8); + } + } + + /// + /// Get minimum Zip feature version required to extract this entry + /// + /// + /// Minimum features are defined as:
+ /// 1.0 - Default value
+ /// 1.1 - File is a volume label
+ /// 2.0 - File is a folder/directory
+ /// 2.0 - File is compressed using Deflate compression
+ /// 2.0 - File is encrypted using traditional encryption
+ /// 2.1 - File is compressed using Deflate64
+ /// 2.5 - File is compressed using PKWARE DCL Implode
+ /// 2.7 - File is a patch data set
+ /// 4.5 - File uses Zip64 format extensions
+ /// 4.6 - File is compressed using BZIP2 compression
+ /// 5.0 - File is encrypted using DES
+ /// 5.0 - File is encrypted using 3DES
+ /// 5.0 - File is encrypted using original RC2 encryption
+ /// 5.0 - File is encrypted using RC4 encryption
+ /// 5.1 - File is encrypted using AES encryption
+ /// 5.1 - File is encrypted using corrected RC2 encryption
+ /// 5.1 - File is encrypted using corrected RC2-64 encryption
+ /// 6.1 - File is encrypted using non-OAEP key wrapping
+ /// 6.2 - Central directory encryption (not confirmed yet)
+ /// 6.3 - File is compressed using LZMA
+ /// 6.3 - File is compressed using PPMD+
+ /// 6.3 - File is encrypted using Blowfish
+ /// 6.3 - File is encrypted using Twofish
+ ///
+ /// + public int Version + { + get + { + // Return recorded version if known. + if (versionToExtract != 0) + // Only lower order byte. High order is O/S file system. + return versionToExtract & 0x00ff; + + if (AESKeySize > 0) + // Ver 5.1 = AES + return ZipConstants.VERSION_AES; + + if (CompressionMethod.BZip2 == method) + return ZipConstants.VersionBZip2; + + if (CentralHeaderRequiresZip64) + return ZipConstants.VersionZip64; + + if (CompressionMethod.Deflated == method || IsDirectory || IsCrypted) + return 20; + + return HasDosAttributes(0x08) ? 11 : 10; + } + } + + /// + /// Get a value indicating whether this entry can be decompressed by the library. + /// + /// This is based on the and + /// whether the compression method is supported. + public bool CanDecompress + => Version <= ZipConstants.VersionMadeBy + && (Version == 10 || Version == 11 || Version == 20 || Version == 45 || Version == 46 || Version == 51) + && IsCompressionMethodSupported(); + + /// + /// Force this entry to be recorded using Zip64 extensions. + /// + public void ForceZip64() => forceZip64_ = true; + + /// + /// Get a value indicating whether Zip64 extensions were forced. + /// + /// A value of true if Zip64 extensions have been forced on; false if not. + public bool IsZip64Forced() => forceZip64_; + + /// + /// Gets a value indicating if the entry requires Zip64 extensions + /// to store the full entry values. + /// + /// A value of true if a local header requires Zip64 extensions; false if not. + public bool LocalHeaderRequiresZip64 + { + get + { + var result = forceZip64_; + + if (!result) + { + var trueCompressedSize = compressedSize; + + if ((versionToExtract == 0) && IsCrypted) + { + trueCompressedSize += (ulong)this.EncryptionOverheadSize; + } + + // TODO: A better estimation of the true limit based on compression overhead should be used + // to determine when an entry should use Zip64. + result = + ((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) && + ((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64)); + } + + return result; + } + } + + /// + /// Get a value indicating whether the central directory entry requires Zip64 extensions to be stored. + /// + public bool CentralHeaderRequiresZip64 + => LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); + + /// + /// Get/Set DosTime value. + /// + /// + /// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107. + /// + public long DosTime + { + get + { + if ((known & Known.Time) == 0) + { + return 0; + } + + var year = (uint)DateTime.Year; + var month = (uint)DateTime.Month; + var day = (uint)DateTime.Day; + var hour = (uint)DateTime.Hour; + var minute = (uint)DateTime.Minute; + var second = (uint)DateTime.Second; + + if (year < 1980) + { + year = 1980; + month = 1; + day = 1; + hour = 0; + minute = 0; + second = 0; + } + else if (year > 2107) + { + year = 2107; + month = 12; + day = 31; + hour = 23; + minute = 59; + second = 59; + } + + return (((year - 1980) & 0x7f) << 25) | + (month << 21) | + (day << 16) | + (hour << 11) | + (minute << 5) | + (second >> 1); + } + + set + { + unchecked + { + var dosTime = (uint)value; + var sec = Math.Min(59, 2 * (dosTime & 0x1f)); + var min = Math.Min(59, (dosTime >> 5) & 0x3f); + var hrs = Math.Min(23, (dosTime >> 11) & 0x1f); + var mon = Math.Max(1, Math.Min(12, (uint)(value >> 21) & 0xf)); + var year = ((dosTime >> 25) & 0x7f) + 1980; + var day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((value >> 16) & 0x1f))); + DateTime = new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Unspecified); + } + } + } + + /// + /// Gets/Sets the time of last modification of the entry. + /// + /// + /// The property is updated to match this as far as possible. + /// + public DateTime DateTime + { + get => dateTime; + + set + { + dateTime = value; + known |= Known.Time; + } + } + + /// + /// Returns the entry name. + /// + /// + /// The unix naming convention is followed. + /// Path components in the entry should always separated by forward slashes ('/'). + /// Dos device names like C: should also be removed. + /// See the class, or + /// + public string Name + { + get => name; + internal set => name = value; + } + + /// + /// Gets/Sets the size of the uncompressed data. + /// + /// + /// The size or -1 if unknown. + /// + /// Setting the size before adding an entry to an archive can help + /// avoid compatibility problems with some archivers which don't understand Zip64 extensions. + public long Size + { + get => (known & Known.Size) != 0 ? (long)size : -1L; + set + { + size = (ulong)value; + known |= Known.Size; + } + } + + /// + /// Gets/Sets the size of the compressed data. + /// + /// + /// The compressed entry size or -1 if unknown. + /// + public long CompressedSize + { + get => (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; + set + { + compressedSize = (ulong)value; + known |= Known.CompressedSize; + } + } + + /// + /// Gets/Sets the crc of the uncompressed data. + /// + /// + /// Crc is not in the range 0..0xffffffffL + /// + /// + /// The crc value or -1 if unknown. + /// + public long Crc + { + get => (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; + set + { + if ((crc & 0xffffffff00000000L) != 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + this.crc = (uint)value; + this.known |= Known.Crc; + } + } + + /// + /// Gets/Sets the compression method. + /// + /// + /// The compression method for this entry + /// + public CompressionMethod CompressionMethod + { + get => method; + set => method = value; + } + + /// + /// Gets the compression method for outputting to the local or central header. + /// Returns same value as CompressionMethod except when AES encrypting, which + /// places 99 in the method and places the real method in the extra data. + /// + internal CompressionMethod CompressionMethodForHeader + => (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; + + /// + /// Gets/Sets the extra data. + /// + /// + /// Extra data is longer than 64KB (0xffff) bytes. + /// + /// + /// Extra data or null if not set. + /// + public byte[] ExtraData + { + // TODO: This is slightly safer but less efficient. Think about whether it should change. + // return (byte[]) extra.Clone(); + get => extra; + + set + { + if (value == null) + { + extra = null; + } + else + { + if (value.Length > 0xffff) + { + throw new System.ArgumentOutOfRangeException(nameof(value)); + } + + extra = new byte[value.Length]; + Array.Copy(value, 0, extra, 0, value.Length); + } + } + } + + /// + /// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256). + /// When setting, only 0 (off), 128 or 256 is supported. + /// + public int AESKeySize + { + get + { + // the strength (1 or 3) is in the entry header + return _aesEncryptionStrength switch + { + 0 => 0,// Not AES + 1 => 128, + 2 => 192,// Not used by WinZip + 3 => 256, + _ => throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength), + }; + } + set + { + _aesEncryptionStrength = value switch + { + 0 => 0, + 128 => 1, + 256 => 3, + _ => throw new ZipException("AESKeySize must be 0, 128 or 256: " + value), + }; + } + } + + /// + /// AES Encryption strength for storage in extra data in entry header. + /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. + /// + internal byte AESEncryptionStrength => (byte)_aesEncryptionStrength; + + /// + /// Returns the length of the salt, in bytes + /// + /// Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. + internal int AESSaltLen => AESKeySize / 16; + + /// + /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) + /// + /// File format: + /// Bytes | Content + /// ---------+--------------------------- + /// Variable | Salt value + /// 2 | Password verification value + /// Variable | Encrypted file data + /// 10 | Authentication code + internal int AESOverheadSize => 12 + AESSaltLen; + + /// + /// Number of extra bytes required to hold the encryption header fields. + /// + internal int EncryptionOverheadSize => + !IsCrypted + // Entry is not encrypted - no overhead + ? 0 + : _aesEncryptionStrength == 0 + // Entry is encrypted using ZipCrypto + ? ZipConstants.CryptoHeaderSize + // Entry is encrypted using AES + : AESOverheadSize; + + /// + /// Process extra data fields updating the entry based on the contents. + /// + /// True if the extra data fields should be handled + /// for a local header, rather than for a central header. + /// + internal void ProcessExtraData(bool localHeader) + { + var extraData = new ZipExtraData(this.extra); + + if (extraData.Find(0x0001)) + { + // Version required to extract is ignored here as some archivers dont set it correctly + // in theory it should be version 45 or higher + + // The recorded size will change but remember that this is zip64. + forceZip64_ = true; + + if (extraData.ValueLength < 4) + { + throw new ZipException("Extra data extended Zip64 information length is invalid"); + } + + // (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between local header & central directory + // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + // ... + // 4.4 Explanation of fields + // ... + // 4.4.8 compressed size: (4 bytes) + // 4.4.9 uncompressed size: (4 bytes) + // + // The size of the file compressed (4.4.8) and uncompressed, + // (4.4.9) respectively. When a decryption header is present it + // will be placed in front of the file data and the value of the + // compressed file size will include the bytes of the decryption + // header. If bit 3 of the general purpose bit flag is set, + // these fields are set to zero in the local header and the + // correct values are put in the data descriptor and + // in the central directory. If an archive is in ZIP64 format + // and the value in this field is 0xFFFFFFFF, the size will be + // in the corresponding 8 byte ZIP64 extended information + // extra field. When encrypting the central directory, if the + // local header is not in ZIP64 format and general purpose bit + // flag 13 is set indicating masking, the value stored for the + // uncompressed size in the Local Header will be zero. + // + // Otherwise there is problem with minizip implementation + if (size == uint.MaxValue) + { + size = (ulong)extraData.ReadLong(); + } + + if (compressedSize == uint.MaxValue) + { + compressedSize = (ulong)extraData.ReadLong(); + } + + if (!localHeader && (offset == uint.MaxValue)) + { + offset = extraData.ReadLong(); + } + + // Disk number on which file starts is ignored + } + else + { + if ( + ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && + ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) + ) + { + throw new ZipException("Zip64 Extended information required but is missing."); + } + } + + DateTime = GetDateTime(extraData) ?? DateTime; + if (method == CompressionMethod.WinZipAES) + { + ProcessAESExtraData(extraData); + } + } + + private static DateTime? GetDateTime(ZipExtraData extraData) + { + // Check for NT timestamp + // NOTE: Disable by default to match behavior of InfoZIP #if RESPECT_NT_TIMESTAMP NTTaggedData ntData = extraData.GetData(); if (ntData != null) return ntData.LastModificationTime; #endif - // Check for Unix timestamp - ExtendedUnixData unixData = extraData.GetData(); - if (unixData != null && unixData.Include.HasFlag(ExtendedUnixData.Flags.ModificationTime)) - return unixData.ModificationTime; - - return null; - } - - // For AES the method in the entry is 99, and the real compression method is in the extradata - private void ProcessAESExtraData(ZipExtraData extraData) - { - if (extraData.Find(0x9901)) - { - // Set version for Zipfile.CreateAndInitDecryptionStream - versionToExtract = ZipConstants.VERSION_AES; // Ver 5.1 = AES see "Version" getter - - // - // Unpack AES extra data field see http://www.winzip.com/aes_info.htm - int length = extraData.ValueLength; // Data size currently 7 - if (length < 7) - throw new ZipException("AES Extra Data Length " + length + " invalid."); - int ver = extraData.ReadShort(); // Version number (1=AE-1 2=AE-2) - int vendorId = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE" - int encrStrength = extraData.ReadByte(); // encryption strength 1 = 128 2 = 192 3 = 256 - int actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file - _aesVer = ver; - _aesEncryptionStrength = encrStrength; - method = (CompressionMethod)actualCompress; - } - else - throw new ZipException("AES Extra Data missing"); - } - - /// - /// Gets/Sets the entry comment. - /// - /// - /// If comment is longer than 0xffff. - /// - /// - /// The comment or null if not set. - /// - /// - /// A comment is only available for entries when read via the class. - /// The class doesn't have the comment data available. - /// - public string Comment - { - get => comment; - set - { - // This test is strictly incorrect as the length is in characters - // while the storage limit is in bytes. - // While the test is partially correct in that a comment of this length or greater - // is definitely invalid, shorter comments may also have an invalid length - // where there are multi-byte characters - // The full test is not possible here however as the code page to apply conversions with - // isn't available. - if ((value != null) && (value.Length > 0xffff)) - { - throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535"); - } - - comment = value; - } - } - - /// - /// Gets a value indicating if the entry is a directory. - /// however. - /// - /// - /// A directory is determined by an entry name with a trailing slash '/'. - /// The external file attributes can also indicate an entry is for a directory. - /// Currently only dos/windows attributes are tested in this manner. - /// The trailing slash convention should always be followed. - /// - public bool IsDirectory - => name.Length > 0 - && (name[name.Length - 1] == '/' || name[name.Length - 1] == '\\') || HasDosAttributes(16); - - /// - /// Get a value of true if the entry appears to be a file; false otherwise - /// - /// - /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. - /// For linux and others the result may be incorrect. - /// - public bool IsFile => !IsDirectory && !HasDosAttributes(8); - - /// - /// Test entry to see if data can be extracted. - /// - /// Returns true if data can be extracted for this entry; false otherwise. - public bool IsCompressionMethodSupported() => IsCompressionMethodSupported(CompressionMethod); - - #region ICloneable Members - - /// - /// Creates a copy of this zip entry. - /// - /// An that is a copy of the current instance. - public object Clone() - { - var result = (ZipEntry)this.MemberwiseClone(); - - // Ensure extra data is unique if it exists. - if (extra != null) - { - result.extra = new byte[extra.Length]; - Array.Copy(extra, 0, result.extra, 0, extra.Length); - } - - return result; - } - - #endregion ICloneable Members - - /// - /// Gets a string representation of this ZipEntry. - /// - /// A readable textual representation of this - public override string ToString() => name; - - /// - /// Test a compression method to see if this library - /// supports extracting data compressed with that method - /// - /// The compression method to test. - /// Returns true if the compression method is supported; false otherwise - public static bool IsCompressionMethodSupported(CompressionMethod method) - => method == CompressionMethod.Deflated - || method == CompressionMethod.Stored - || method == CompressionMethod.BZip2; - - /// - /// Cleans a name making it conform to Zip file conventions. - /// Devices names ('c:\') and UNC share names ('\\server\share') are removed - /// and forward slashes ('\') are converted to back slashes ('/'). - /// Names are made relative by trimming leading slashes which is compatible - /// with the ZIP naming convention. - /// - /// The name to clean - /// The 'cleaned' name. - /// - /// The Zip name transform class is more flexible. - /// - public static string CleanName(string name) - { - if (name == null) - { - return string.Empty; - } - - if (Path.IsPathRooted(name)) - { - // NOTE: - // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt - name = name.Substring(Path.GetPathRoot(name).Length); - } - - name = name.Replace(@"\", "/"); - - while ((name.Length > 0) && (name[0] == '/')) - { - name = name.Remove(0, 1); - } - return name; - } - - #region Instance Fields - - private Known known; - private int externalFileAttributes = -1; // contains external attributes (O/S dependant) - - private ushort versionMadeBy; // Contains host system and version information - // only relevant for central header entries - - private string name; - private ulong size; - private ulong compressedSize; - private ushort versionToExtract; // Version required to extract (library handles <= 2.0) - private uint crc; - private DateTime dateTime; - - private CompressionMethod method = CompressionMethod.Deflated; - private byte[] extra; - private string comment; - - private int flags; // general purpose bit flags - - private long zipFileIndex = -1; // used by ZipFile - private long offset; // used by ZipFile and ZipOutputStream - - private bool forceZip64_; - private byte cryptoCheckValue_; - private int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used. - private int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256 - - #endregion Instance Fields - } + // Check for Unix timestamp + var unixData = extraData.GetData(); + return unixData != null && unixData.Include.HasFlag(ExtendedUnixData.Flags.ModificationTime) ? unixData.ModificationTime : null; + } + + // For AES the method in the entry is 99, and the real compression method is in the extradata + private void ProcessAESExtraData(ZipExtraData extraData) + { + if (extraData.Find(0x9901)) + { + // Set version for Zipfile.CreateAndInitDecryptionStream + versionToExtract = ZipConstants.VERSION_AES; // Ver 5.1 = AES see "Version" getter + + // + // Unpack AES extra data field see http://www.winzip.com/aes_info.htm + var length = extraData.ValueLength; // Data size currently 7 + if (length < 7) + throw new ZipException("AES Extra Data Length " + length + " invalid."); + var ver = extraData.ReadShort(); // Version number (1=AE-1 2=AE-2) + + _ = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE" + var encrStrength = extraData.ReadByte(); // encryption strength 1 = 128 2 = 192 3 = 256 + var actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file + _aesVer = ver; + _aesEncryptionStrength = encrStrength; + method = (CompressionMethod)actualCompress; + } + else + throw new ZipException("AES Extra Data missing"); + } + + /// + /// Gets/Sets the entry comment. + /// + /// + /// If comment is longer than 0xffff. + /// + /// + /// The comment or null if not set. + /// + /// + /// A comment is only available for entries when read via the class. + /// The class doesn't have the comment data available. + /// + public string Comment + { + get => comment; + set + { + // This test is strictly incorrect as the length is in characters + // while the storage limit is in bytes. + // While the test is partially correct in that a comment of this length or greater + // is definitely invalid, shorter comments may also have an invalid length + // where there are multi-byte characters + // The full test is not possible here however as the code page to apply conversions with + // isn't available. + if ((value != null) && (value.Length > 0xffff)) + { + throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535"); + } + + comment = value; + } + } + + /// + /// Gets a value indicating if the entry is a directory. + /// however. + /// + /// + /// A directory is determined by an entry name with a trailing slash '/'. + /// The external file attributes can also indicate an entry is for a directory. + /// Currently only dos/windows attributes are tested in this manner. + /// The trailing slash convention should always be followed. + /// + public bool IsDirectory + => name.Length > 0 + && (name[^1] == '/' || name[^1] == '\\') || HasDosAttributes(16); + + /// + /// Get a value of true if the entry appears to be a file; false otherwise + /// + /// + /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. + /// For linux and others the result may be incorrect. + /// + public bool IsFile => !IsDirectory && !HasDosAttributes(8); + + /// + /// Test entry to see if data can be extracted. + /// + /// Returns true if data can be extracted for this entry; false otherwise. + public bool IsCompressionMethodSupported() => IsCompressionMethodSupported(CompressionMethod); + + #region ICloneable Members + + /// + /// Creates a copy of this zip entry. + /// + /// An that is a copy of the current instance. + public object Clone() + { + var result = (ZipEntry)this.MemberwiseClone(); + + // Ensure extra data is unique if it exists. + if (extra != null) + { + result.extra = new byte[extra.Length]; + Array.Copy(extra, 0, result.extra, 0, extra.Length); + } + + return result; + } + + #endregion ICloneable Members + + /// + /// Gets a string representation of this ZipEntry. + /// + /// A readable textual representation of this + public override string ToString() => name; + + /// + /// Test a compression method to see if this library + /// supports extracting data compressed with that method + /// + /// The compression method to test. + /// Returns true if the compression method is supported; false otherwise + public static bool IsCompressionMethodSupported(CompressionMethod method) + => method is CompressionMethod.Deflated + or CompressionMethod.Stored + or CompressionMethod.BZip2; + + /// + /// Cleans a name making it conform to Zip file conventions. + /// Devices names ('c:\') and UNC share names ('\\server\share') are removed + /// and forward slashes ('\') are converted to back slashes ('/'). + /// Names are made relative by trimming leading slashes which is compatible + /// with the ZIP naming convention. + /// + /// The name to clean + /// The 'cleaned' name. + /// + /// The Zip name transform class is more flexible. + /// + public static string CleanName(string name) + { + if (name == null) + { + return string.Empty; + } + + if (Path.IsPathRooted(name)) + { + // NOTE: + // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt + name = name[Path.GetPathRoot(name).Length..]; + } + + name = name.Replace(@"\", "/"); + + while ((name.Length > 0) && (name[0] == '/')) + { + name = name.Remove(0, 1); + } + return name; + } + + #region Instance Fields + + private Known known; + private int externalFileAttributes = -1; // contains external attributes (O/S dependant) + + private ushort versionMadeBy; // Contains host system and version information + // only relevant for central header entries + + private string name; + private ulong size; + private ulong compressedSize; + private ushort versionToExtract; // Version required to extract (library handles <= 2.0) + private uint crc; + private DateTime dateTime; + + private CompressionMethod method = CompressionMethod.Deflated; + private byte[] extra; + private string comment; + + private int flags; // general purpose bit flags + + private long zipFileIndex = -1; // used by ZipFile + private long offset; // used by ZipFile and ZipOutputStream + + private bool forceZip64_; + private byte cryptoCheckValue_; + private int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used. + private int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256 + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs index 83846c504..e3a61fd02 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs @@ -1,32 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Text; +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +/// +/// General ZipEntry helper extensions +/// +public static class ZipEntryExtensions { - /// - /// General ZipEntry helper extensions - /// - public static class ZipEntryExtensions - { - /// - /// Efficiently check if a flag is set without enum un-/boxing - /// - /// - /// - /// Returns whether the flag was set - public static bool HasFlag(this ZipEntry entry, GeneralBitFlags flag) - => (entry.Flags & (int) flag) != 0; + /// + /// Efficiently check if a flag is set without enum un-/boxing + /// + /// + /// + /// Returns whether the flag was set + public static bool HasFlag(this ZipEntry entry, GeneralBitFlags flag) + => (entry.Flags & (int)flag) != 0; - /// - /// Efficiently set a flag without enum un-/boxing - /// - /// - /// - /// Whether the passed flag should be set (1) or cleared (0) - public static void SetFlag(this ZipEntry entry, GeneralBitFlags flag, bool enabled = true) - => entry.Flags = enabled - ? entry.Flags | (int) flag - : entry.Flags & ~(int) flag; - } + /// + /// Efficiently set a flag without enum un-/boxing + /// + /// + /// + /// Whether the passed flag should be set (1) or cleared (0) + public static void SetFlag(this ZipEntry entry, GeneralBitFlags flag, bool enabled = true) + => entry.Flags = enabled + ? entry.Flags | (int)flag + : entry.Flags & ~(int)flag; } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs index 5c9c75728..c34a38056 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs @@ -2,374 +2,324 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// Basic implementation of +/// +public class ZipEntryFactory : IEntryFactory { - /// - /// Basic implementation of - /// - public class ZipEntryFactory : IEntryFactory - { - #region Enumerations - - /// - /// Defines the possible values to be used for the . - /// - public enum TimeSetting - { - /// - /// Use the recorded LastWriteTime value for the file. - /// - LastWriteTime, - - /// - /// Use the recorded LastWriteTimeUtc value for the file - /// - LastWriteTimeUtc, - - /// - /// Use the recorded CreateTime value for the file. - /// - CreateTime, - - /// - /// Use the recorded CreateTimeUtc value for the file. - /// - CreateTimeUtc, - - /// - /// Use the recorded LastAccessTime value for the file. - /// - LastAccessTime, - - /// - /// Use the recorded LastAccessTimeUtc value for the file. - /// - LastAccessTimeUtc, - - /// - /// Use a fixed value. - /// - /// The actual value used can be - /// specified via the constructor or - /// using the with the setting set - /// to which will use the when this class was constructed. - /// The property can also be used to set this value. - Fixed, - } - - #endregion Enumerations - - #region Constructors - - /// - /// Initialise a new instance of the class. - /// - /// A default , and the LastWriteTime for files is used. - public ZipEntryFactory() - { - nameTransform_ = new ZipNameTransform(); - isUnicodeText_ = ZipStrings.UseUnicode; - } - - /// - /// Initialise a new instance of using the specified - /// - /// The time setting to use when creating Zip entries. - public ZipEntryFactory(TimeSetting timeSetting) : this() - { - timeSetting_ = timeSetting; - } - - /// - /// Initialise a new instance of using the specified - /// - /// The time to set all values to. - public ZipEntryFactory(DateTime time) : this() - { - timeSetting_ = TimeSetting.Fixed; - FixedDateTime = time; - } - - #endregion Constructors - - #region Properties - - /// - /// Get / set the to be used when creating new values. - /// - /// - /// Setting this property to null will cause a default name transform to be used. - /// - public INameTransform NameTransform - { - get { return nameTransform_; } - set - { - if (value == null) - { - nameTransform_ = new ZipNameTransform(); - } - else - { - nameTransform_ = value; - } - } - } - - /// - /// Get / set the in use. - /// - public TimeSetting Setting - { - get { return timeSetting_; } - set { timeSetting_ = value; } - } - - /// - /// Get / set the value to use when is set to - /// - public DateTime FixedDateTime - { - get { return fixedDateTime_; } - set - { - if (value.Year < 1970) - { - throw new ArgumentException("Value is too old to be valid", nameof(value)); - } - fixedDateTime_ = value; - } - } - - /// - /// A bitmask defining the attributes to be retrieved from the actual file. - /// - /// The default is to get all possible attributes from the actual file. - public int GetAttributes - { - get { return getAttributes_; } - set { getAttributes_ = value; } - } - - /// - /// A bitmask defining which attributes are to be set on. - /// - /// By default no attributes are set on. - public int SetAttributes - { - get { return setAttributes_; } - set { setAttributes_ = value; } - } - - /// - /// Get set a value indicating whether unidoce text should be set on. - /// - public bool IsUnicodeText - { - get { return isUnicodeText_; } - set { isUnicodeText_ = value; } - } - - #endregion Properties - - #region IEntryFactory Members - - /// - /// Make a new for a file. - /// - /// The name of the file to create a new entry for. - /// Returns a new based on the . - public ZipEntry MakeFileEntry(string fileName) - { - return MakeFileEntry(fileName, null, true); - } - - /// - /// Make a new for a file. - /// - /// The name of the file to create a new entry for. - /// If true entry detail is retrieved from the file system if the file exists. - /// Returns a new based on the . - public ZipEntry MakeFileEntry(string fileName, bool useFileSystem) - { - return MakeFileEntry(fileName, null, useFileSystem); - } - - /// - /// Make a new from a name. - /// - /// The name of the file to create a new entry for. - /// An alternative name to be used for the new entry. Null if not applicable. - /// If true entry detail is retrieved from the file system if the file exists. - /// Returns a new based on the . - public ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem) - { - var result = new ZipEntry(nameTransform_.TransformFile(!string.IsNullOrEmpty(entryName) ? entryName : fileName)); - result.IsUnicodeText = isUnicodeText_; - - int externalAttributes = 0; - bool useAttributes = (setAttributes_ != 0); - - FileInfo fi = null; - if (useFileSystem) - { - fi = new FileInfo(fileName); - } - - if ((fi != null) && fi.Exists) - { - switch (timeSetting_) - { - case TimeSetting.CreateTime: - result.DateTime = fi.CreationTime; - break; - - case TimeSetting.CreateTimeUtc: - result.DateTime = fi.CreationTimeUtc; - break; - - case TimeSetting.LastAccessTime: - result.DateTime = fi.LastAccessTime; - break; - - case TimeSetting.LastAccessTimeUtc: - result.DateTime = fi.LastAccessTimeUtc; - break; - - case TimeSetting.LastWriteTime: - result.DateTime = fi.LastWriteTime; - break; - - case TimeSetting.LastWriteTimeUtc: - result.DateTime = fi.LastWriteTimeUtc; - break; - - case TimeSetting.Fixed: - result.DateTime = fixedDateTime_; - break; - - default: - throw new ZipException("Unhandled time setting in MakeFileEntry"); - } - - result.Size = fi.Length; - - useAttributes = true; - externalAttributes = ((int)fi.Attributes & getAttributes_); - } - else - { - if (timeSetting_ == TimeSetting.Fixed) - { - result.DateTime = fixedDateTime_; - } - } - - if (useAttributes) - { - externalAttributes |= setAttributes_; - result.ExternalFileAttributes = externalAttributes; - } - - return result; - } - - /// - /// Make a new for a directory. - /// - /// The raw untransformed name for the new directory - /// Returns a new representing a directory. - public ZipEntry MakeDirectoryEntry(string directoryName) - { - return MakeDirectoryEntry(directoryName, true); - } - - /// - /// Make a new for a directory. - /// - /// The raw untransformed name for the new directory - /// If true entry detail is retrieved from the file system if the file exists. - /// Returns a new representing a directory. - public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem) - { - var result = new ZipEntry(nameTransform_.TransformDirectory(directoryName)); - result.IsUnicodeText = isUnicodeText_; - result.Size = 0; - - int externalAttributes = 0; - - DirectoryInfo di = null; - - if (useFileSystem) - { - di = new DirectoryInfo(directoryName); - } - - if ((di != null) && di.Exists) - { - switch (timeSetting_) - { - case TimeSetting.CreateTime: - result.DateTime = di.CreationTime; - break; - - case TimeSetting.CreateTimeUtc: - result.DateTime = di.CreationTimeUtc; - break; - - case TimeSetting.LastAccessTime: - result.DateTime = di.LastAccessTime; - break; - - case TimeSetting.LastAccessTimeUtc: - result.DateTime = di.LastAccessTimeUtc; - break; - - case TimeSetting.LastWriteTime: - result.DateTime = di.LastWriteTime; - break; - - case TimeSetting.LastWriteTimeUtc: - result.DateTime = di.LastWriteTimeUtc; - break; - - case TimeSetting.Fixed: - result.DateTime = fixedDateTime_; - break; - - default: - throw new ZipException("Unhandled time setting in MakeDirectoryEntry"); - } - - externalAttributes = ((int)di.Attributes & getAttributes_); - } - else - { - if (timeSetting_ == TimeSetting.Fixed) - { - result.DateTime = fixedDateTime_; - } - } - - // Always set directory attribute on. - externalAttributes |= (setAttributes_ | 16); - result.ExternalFileAttributes = externalAttributes; - - return result; - } - - #endregion IEntryFactory Members - - #region Instance Fields - - private INameTransform nameTransform_; - private DateTime fixedDateTime_ = DateTime.Now; - private TimeSetting timeSetting_ = TimeSetting.LastWriteTime; - private bool isUnicodeText_; - - private int getAttributes_ = -1; - private int setAttributes_; - - #endregion Instance Fields - } + #region Enumerations + + /// + /// Defines the possible values to be used for the . + /// + public enum TimeSetting + { + /// + /// Use the recorded LastWriteTime value for the file. + /// + LastWriteTime, + + /// + /// Use the recorded LastWriteTimeUtc value for the file + /// + LastWriteTimeUtc, + + /// + /// Use the recorded CreateTime value for the file. + /// + CreateTime, + + /// + /// Use the recorded CreateTimeUtc value for the file. + /// + CreateTimeUtc, + + /// + /// Use the recorded LastAccessTime value for the file. + /// + LastAccessTime, + + /// + /// Use the recorded LastAccessTimeUtc value for the file. + /// + LastAccessTimeUtc, + + /// + /// Use a fixed value. + /// + /// The actual value used can be + /// specified via the constructor or + /// using the with the setting set + /// to which will use the when this class was constructed. + /// The property can also be used to set this value. + Fixed, + } + + #endregion Enumerations + + #region Constructors + + /// + /// Initialise a new instance of the class. + /// + /// A default , and the LastWriteTime for files is used. + public ZipEntryFactory() + { + nameTransform_ = new ZipNameTransform(); + isUnicodeText_ = ZipStrings.UseUnicode; + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time setting to use when creating Zip entries. + public ZipEntryFactory(TimeSetting timeSetting) : this() + { + timeSetting_ = timeSetting; + } + + /// + /// Initialise a new instance of using the specified + /// + /// The time to set all values to. + public ZipEntryFactory(DateTime time) : this() + { + timeSetting_ = TimeSetting.Fixed; + FixedDateTime = time; + } + + #endregion Constructors + + #region Properties + + /// + /// Get / set the to be used when creating new values. + /// + /// + /// Setting this property to null will cause a default name transform to be used. + /// + public INameTransform NameTransform + { + get { return nameTransform_; } + set + { + nameTransform_ = value == null ? new ZipNameTransform() : value; + } + } + + /// + /// Get / set the in use. + /// + public TimeSetting Setting + { + get { return timeSetting_; } + set { timeSetting_ = value; } + } + + /// + /// Get / set the value to use when is set to + /// + public DateTime FixedDateTime + { + get { return fixedDateTime_; } + set + { + if (value.Year < 1970) + { + throw new ArgumentException("Value is too old to be valid", nameof(value)); + } + fixedDateTime_ = value; + } + } + + /// + /// A bitmask defining the attributes to be retrieved from the actual file. + /// + /// The default is to get all possible attributes from the actual file. + public int GetAttributes + { + get { return getAttributes_; } + set { getAttributes_ = value; } + } + + /// + /// A bitmask defining which attributes are to be set on. + /// + /// By default no attributes are set on. + public int SetAttributes + { + get { return setAttributes_; } + set { setAttributes_ = value; } + } + + /// + /// Get set a value indicating whether unidoce text should be set on. + /// + public bool IsUnicodeText + { + get { return isUnicodeText_; } + set { isUnicodeText_ = value; } + } + + #endregion Properties + + #region IEntryFactory Members + + /// + /// Make a new for a file. + /// + /// The name of the file to create a new entry for. + /// Returns a new based on the . + public ZipEntry MakeFileEntry(string fileName) + { + return MakeFileEntry(fileName, null, true); + } + + /// + /// Make a new for a file. + /// + /// The name of the file to create a new entry for. + /// If true entry detail is retrieved from the file system if the file exists. + /// Returns a new based on the . + public ZipEntry MakeFileEntry(string fileName, bool useFileSystem) + { + return MakeFileEntry(fileName, null, useFileSystem); + } + + /// + /// Make a new from a name. + /// + /// The name of the file to create a new entry for. + /// An alternative name to be used for the new entry. Null if not applicable. + /// If true entry detail is retrieved from the file system if the file exists. + /// Returns a new based on the . + public ZipEntry MakeFileEntry(string fileName, string entryName, bool useFileSystem) + { + var result = new ZipEntry(nameTransform_.TransformFile(!string.IsNullOrEmpty(entryName) ? entryName : fileName)) + { + IsUnicodeText = isUnicodeText_ + }; + + var externalAttributes = 0; + var useAttributes = setAttributes_ != 0; + + FileInfo fi = null; + if (useFileSystem) + { + fi = new FileInfo(fileName); + } + + if ((fi != null) && fi.Exists) + { + result.DateTime = timeSetting_ switch + { + TimeSetting.CreateTime => fi.CreationTime, + TimeSetting.CreateTimeUtc => fi.CreationTimeUtc, + TimeSetting.LastAccessTime => fi.LastAccessTime, + TimeSetting.LastAccessTimeUtc => fi.LastAccessTimeUtc, + TimeSetting.LastWriteTime => fi.LastWriteTime, + TimeSetting.LastWriteTimeUtc => fi.LastWriteTimeUtc, + TimeSetting.Fixed => fixedDateTime_, + _ => throw new ZipException("Unhandled time setting in MakeFileEntry"), + }; + result.Size = fi.Length; + + useAttributes = true; + externalAttributes = (int)fi.Attributes & getAttributes_; + } + else + { + if (timeSetting_ == TimeSetting.Fixed) + { + result.DateTime = fixedDateTime_; + } + } + + if (useAttributes) + { + externalAttributes |= setAttributes_; + result.ExternalFileAttributes = externalAttributes; + } + + return result; + } + + /// + /// Make a new for a directory. + /// + /// The raw untransformed name for the new directory + /// Returns a new representing a directory. + public ZipEntry MakeDirectoryEntry(string directoryName) + { + return MakeDirectoryEntry(directoryName, true); + } + + /// + /// Make a new for a directory. + /// + /// The raw untransformed name for the new directory + /// If true entry detail is retrieved from the file system if the file exists. + /// Returns a new representing a directory. + public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem) + { + var result = new ZipEntry(nameTransform_.TransformDirectory(directoryName)) + { + IsUnicodeText = isUnicodeText_, + Size = 0 + }; + + var externalAttributes = 0; + + DirectoryInfo di = null; + + if (useFileSystem) + { + di = new DirectoryInfo(directoryName); + } + + if ((di != null) && di.Exists) + { + result.DateTime = timeSetting_ switch + { + TimeSetting.CreateTime => di.CreationTime, + TimeSetting.CreateTimeUtc => di.CreationTimeUtc, + TimeSetting.LastAccessTime => di.LastAccessTime, + TimeSetting.LastAccessTimeUtc => di.LastAccessTimeUtc, + TimeSetting.LastWriteTime => di.LastWriteTime, + TimeSetting.LastWriteTimeUtc => di.LastWriteTimeUtc, + TimeSetting.Fixed => fixedDateTime_, + _ => throw new ZipException("Unhandled time setting in MakeDirectoryEntry"), + }; + externalAttributes = (int)di.Attributes & getAttributes_; + } + else + { + if (timeSetting_ == TimeSetting.Fixed) + { + result.DateTime = fixedDateTime_; + } + } + + // Always set directory attribute on. + externalAttributes |= setAttributes_ | 16; + result.ExternalFileAttributes = externalAttributes; + + return result; + } + + #endregion IEntryFactory Members + + #region Instance Fields + + private INameTransform nameTransform_; + private DateTime fixedDateTime_ = DateTime.Now; + private TimeSetting timeSetting_ = TimeSetting.LastWriteTime; + private bool isUnicodeText_; + + private int getAttributes_ = -1; + private int setAttributes_; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs index c67160268..25c36df61 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs @@ -1,54 +1,53 @@ using System; using System.Runtime.Serialization; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// ZipException represents exceptions specific to Zip classes and code. +/// +[Serializable] +public class ZipException : SharpZipBaseException { - /// - /// ZipException represents exceptions specific to Zip classes and code. - /// - [Serializable] - public class ZipException : SharpZipBaseException - { - /// - /// Initialise a new instance of . - /// - public ZipException() - { - } + /// + /// Initialise a new instance of . + /// + public ZipException() + { + } - /// - /// Initialise a new instance of with its message string. - /// - /// A that describes the error. - public ZipException(string message) - : base(message) - { - } + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public ZipException(string message) + : base(message) + { + } - /// - /// Initialise a new instance of . - /// - /// A that describes the error. - /// The that caused this exception. - public ZipException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public ZipException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the ZipException class with serialized data. - /// - /// - /// The System.Runtime.Serialization.SerializationInfo that holds the serialized - /// object data about the exception being thrown. - /// - /// - /// The System.Runtime.Serialization.StreamingContext that contains contextual information - /// about the source or destination. - /// - protected ZipException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } + /// + /// Initializes a new instance of the ZipException class with serialized data. + /// + /// + /// The System.Runtime.Serialization.SerializationInfo that holds the serialized + /// object data about the exception being thrown. + /// + /// + /// The System.Runtime.Serialization.StreamingContext that contains contextual information + /// about the source or destination. + /// + protected ZipException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs index 0ff1f04c4..f885e8d54 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs @@ -1,980 +1,952 @@ +using MelonLoader.ICSharpCode.SharpZipLib.Core; using System; using System.IO; -using MelonLoader.ICSharpCode.SharpZipLib.Core; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +// TODO: Sort out whether tagged data is useful and what a good implementation might look like. +// Its just a sketch of an idea at the moment. + +/// +/// ExtraData tagged value interface. +/// +public interface ITaggedData +{ + /// + /// Get the ID for this tagged data value. + /// + short TagID { get; } + + /// + /// Set the contents of this instance from the data passed. + /// + /// The data to extract contents from. + /// The offset to begin extracting data from. + /// The number of bytes to extract. + void SetData(byte[] data, int offset, int count); + + /// + /// Get the data representing this instance. + /// + /// Returns the data for this instance. + byte[] GetData(); +} + +/// +/// A raw binary tagged value +/// +public class RawTaggedData : ITaggedData +{ + /// + /// Initialise a new instance. + /// + /// The tag ID. + public RawTaggedData(short tag) + { + _tag = tag; + } + + #region ITaggedData Members + + /// + /// Get the ID for this tagged data value. + /// + public short TagID + { + get { return _tag; } + set { _tag = value; } + } + + /// + /// Set the data from the raw values provided. + /// + /// The raw data to extract values from. + /// The index to start extracting values from. + /// The number of bytes available. + public void SetData(byte[] data, int offset, int count) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + _data = new byte[count]; + Array.Copy(data, offset, _data, 0, count); + } + + /// + /// Get the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] GetData() + { + return _data; + } + + #endregion ITaggedData Members + + /// + /// Get /set the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] Data + { + get { return _data; } + set { _data = value; } + } + + #region Instance Fields + + /// + /// The tag ID for this instance. + /// + private short _tag; + + private byte[] _data; + + #endregion Instance Fields +} + +/// +/// Class representing extended unix date time values. +/// +public class ExtendedUnixData : ITaggedData +{ + /// + /// Flags indicate which values are included in this instance. + /// + [Flags] + public enum Flags : byte + { + /// + /// The modification time is included + /// + ModificationTime = 0x01, + + /// + /// The access time is included + /// + AccessTime = 0x02, + + /// + /// The create time is included. + /// + CreateTime = 0x04, + } + + #region ITaggedData Members + + /// + /// Get the ID + /// + public short TagID + { + get { return 0x5455; } + } + + /// + /// Set the data from the raw values provided. + /// + /// The raw data to extract values from. + /// The index to start extracting values from. + /// The number of bytes available. + public void SetData(byte[] data, int index, int count) + { + using var ms = new MemoryStream(data, index, count, false); + using var helperStream = new ZipHelperStream(ms); + // bit 0 if set, modification time is present + // bit 1 if set, access time is present + // bit 2 if set, creation time is present + + _flags = (Flags)helperStream.ReadByte(); + if ((_flags & Flags.ModificationTime) != 0) + { + var iTime = helperStream.ReadLEInt(); + + _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + new TimeSpan(0, 0, 0, iTime, 0); + + // Central-header version is truncated after modification time + if (count <= 5) + return; + } + + if ((_flags & Flags.AccessTime) != 0) + { + var iTime = helperStream.ReadLEInt(); + + _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + new TimeSpan(0, 0, 0, iTime, 0); + } + + if ((_flags & Flags.CreateTime) != 0) + { + var iTime = helperStream.ReadLEInt(); + + _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + new TimeSpan(0, 0, 0, iTime, 0); + } + } + + /// + /// Get the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] GetData() + { + using var ms = new MemoryStream(); + using var helperStream = new ZipHelperStream(ms); + helperStream.IsStreamOwner = false; + helperStream.WriteByte((byte)_flags); // Flags + if ((_flags & Flags.ModificationTime) != 0) + { + var span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + var seconds = (int)span.TotalSeconds; + helperStream.WriteLEInt(seconds); + } + if ((_flags & Flags.AccessTime) != 0) + { + var span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + var seconds = (int)span.TotalSeconds; + helperStream.WriteLEInt(seconds); + } + if ((_flags & Flags.CreateTime) != 0) + { + var span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + var seconds = (int)span.TotalSeconds; + helperStream.WriteLEInt(seconds); + } + return ms.ToArray(); + } + + #endregion ITaggedData Members + + /// + /// Test a value to see if is valid and can be represented here. + /// + /// The value to test. + /// Returns true if the value is valid and can be represented; false if not. + /// The standard Unix time is a signed integer data type, directly encoding the Unix time number, + /// which is the number of seconds since 1970-01-01. + /// Being 32 bits means the values here cover a range of about 136 years. + /// The minimum representable time is 1901-12-13 20:45:52, + /// and the maximum representable time is 2038-01-19 03:14:07. + /// + public static bool IsValidValue(DateTime value) + { + return (value >= new DateTime(1901, 12, 13, 20, 45, 52)) || + (value <= new DateTime(2038, 1, 19, 03, 14, 07)); + } + + /// + /// Get /set the Modification Time + /// + /// + /// + public DateTime ModificationTime + { + get { return _modificationTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _flags |= Flags.ModificationTime; + _modificationTime = value; + } + } + + /// + /// Get / set the Access Time + /// + /// + /// + public DateTime AccessTime + { + get { return _lastAccessTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _flags |= Flags.AccessTime; + _lastAccessTime = value; + } + } + + /// + /// Get / Set the Create Time + /// + /// + /// + public DateTime CreateTime + { + get { return _createTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _flags |= Flags.CreateTime; + _createTime = value; + } + } + + /// + /// Get/set the values to include. + /// + public Flags Include + { + get { return _flags; } + set { _flags = value; } + } + + #region Instance Fields + + private Flags _flags; + private DateTime _modificationTime = new(1970, 1, 1); + private DateTime _lastAccessTime = new(1970, 1, 1); + private DateTime _createTime = new(1970, 1, 1); + + #endregion Instance Fields +} + +/// +/// Class handling NT date time values. +/// +public class NTTaggedData : ITaggedData +{ + /// + /// Get the ID for this tagged data value. + /// + public short TagID + { + get { return 10; } + } + + /// + /// Set the data from the raw values provided. + /// + /// The raw data to extract values from. + /// The index to start extracting values from. + /// The number of bytes available. + public void SetData(byte[] data, int index, int count) + { + using var ms = new MemoryStream(data, index, count, false); + using var helperStream = new ZipHelperStream(ms); + helperStream.ReadLEInt(); // Reserved + while (helperStream.Position < helperStream.Length) + { + var ntfsTag = helperStream.ReadLEShort(); + var ntfsLength = helperStream.ReadLEShort(); + if (ntfsTag == 1) + { + if (ntfsLength >= 24) + { + var lastModificationTicks = helperStream.ReadLELong(); + _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks); + + var lastAccessTicks = helperStream.ReadLELong(); + _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks); + + var createTimeTicks = helperStream.ReadLELong(); + _createTime = DateTime.FromFileTimeUtc(createTimeTicks); + } + break; + } + else + { + // An unknown NTFS tag so simply skip it. + helperStream.Seek(ntfsLength, SeekOrigin.Current); + } + } + } + + /// + /// Get the binary data representing this instance. + /// + /// The raw binary data representing this instance. + public byte[] GetData() + { + using var ms = new MemoryStream(); + using var helperStream = new ZipHelperStream(ms); + helperStream.IsStreamOwner = false; + helperStream.WriteLEInt(0); // Reserved + helperStream.WriteLEShort(1); // Tag + helperStream.WriteLEShort(24); // Length = 3 x 8. + helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc()); + helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc()); + helperStream.WriteLELong(_createTime.ToFileTimeUtc()); + return ms.ToArray(); + } + + /// + /// Test a valuie to see if is valid and can be represented here. + /// + /// The value to test. + /// Returns true if the value is valid and can be represented; false if not. + /// + /// NTFS filetimes are 64-bit unsigned integers, stored in Intel + /// (least significant byte first) byte order. They determine the + /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", + /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit + /// + public static bool IsValidValue(DateTime value) + { + var result = true; + try + { + value.ToFileTimeUtc(); + } + catch + { + result = false; + } + return result; + } + + /// + /// Get/set the last modification time. + /// + public DateTime LastModificationTime + { + get { return _lastModificationTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _lastModificationTime = value; + } + } + + /// + /// Get /set the create time + /// + public DateTime CreateTime + { + get { return _createTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _createTime = value; + } + } + + /// + /// Get /set the last access time. + /// + public DateTime LastAccessTime + { + get { return _lastAccessTime; } + set + { + if (!IsValidValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + _lastAccessTime = value; + } + } + + #region Instance Fields + + private DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0); + private DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0); + private DateTime _createTime = DateTime.FromFileTimeUtc(0); + + #endregion Instance Fields +} + +/// +/// A factory that creates tagged data instances. +/// +internal interface ITaggedDataFactory +{ + /// + /// Get data for a specific tag value. + /// + /// The tag ID to find. + /// The data to search. + /// The offset to begin extracting data from. + /// The number of bytes to extract. + /// The located value found, or null if not found. + ITaggedData Create(short tag, byte[] data, int offset, int count); +} + +/// +/// +/// A class to handle the extra data field for Zip entries +/// +/// +/// Extra data contains 0 or more values each prefixed by a header tag and length. +/// They contain zero or more bytes of actual data. +/// The data is held internally using a copy on write strategy. This is more efficient but +/// means that for extra data created by passing in data can have the values modified by the caller +/// in some circumstances. +/// +public sealed class ZipExtraData : IDisposable { - // TODO: Sort out whether tagged data is useful and what a good implementation might look like. - // Its just a sketch of an idea at the moment. - - /// - /// ExtraData tagged value interface. - /// - public interface ITaggedData - { - /// - /// Get the ID for this tagged data value. - /// - short TagID { get; } - - /// - /// Set the contents of this instance from the data passed. - /// - /// The data to extract contents from. - /// The offset to begin extracting data from. - /// The number of bytes to extract. - void SetData(byte[] data, int offset, int count); - - /// - /// Get the data representing this instance. - /// - /// Returns the data for this instance. - byte[] GetData(); - } - - /// - /// A raw binary tagged value - /// - public class RawTaggedData : ITaggedData - { - /// - /// Initialise a new instance. - /// - /// The tag ID. - public RawTaggedData(short tag) - { - _tag = tag; - } - - #region ITaggedData Members - - /// - /// Get the ID for this tagged data value. - /// - public short TagID - { - get { return _tag; } - set { _tag = value; } - } - - /// - /// Set the data from the raw values provided. - /// - /// The raw data to extract values from. - /// The index to start extracting values from. - /// The number of bytes available. - public void SetData(byte[] data, int offset, int count) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - _data = new byte[count]; - Array.Copy(data, offset, _data, 0, count); - } - - /// - /// Get the binary data representing this instance. - /// - /// The raw binary data representing this instance. - public byte[] GetData() - { - return _data; - } - - #endregion ITaggedData Members - - /// - /// Get /set the binary data representing this instance. - /// - /// The raw binary data representing this instance. - public byte[] Data - { - get { return _data; } - set { _data = value; } - } - - #region Instance Fields - - /// - /// The tag ID for this instance. - /// - private short _tag; - - private byte[] _data; - - #endregion Instance Fields - } - - /// - /// Class representing extended unix date time values. - /// - public class ExtendedUnixData : ITaggedData - { - /// - /// Flags indicate which values are included in this instance. - /// - [Flags] - public enum Flags : byte - { - /// - /// The modification time is included - /// - ModificationTime = 0x01, - - /// - /// The access time is included - /// - AccessTime = 0x02, - - /// - /// The create time is included. - /// - CreateTime = 0x04, - } - - #region ITaggedData Members - - /// - /// Get the ID - /// - public short TagID - { - get { return 0x5455; } - } - - /// - /// Set the data from the raw values provided. - /// - /// The raw data to extract values from. - /// The index to start extracting values from. - /// The number of bytes available. - public void SetData(byte[] data, int index, int count) - { - using (MemoryStream ms = new MemoryStream(data, index, count, false)) - using (ZipHelperStream helperStream = new ZipHelperStream(ms)) - { - // bit 0 if set, modification time is present - // bit 1 if set, access time is present - // bit 2 if set, creation time is present - - _flags = (Flags)helperStream.ReadByte(); - if (((_flags & Flags.ModificationTime) != 0)) - { - int iTime = helperStream.ReadLEInt(); - - _modificationTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + - new TimeSpan(0, 0, 0, iTime, 0); - - // Central-header version is truncated after modification time - if (count <= 5) return; - } - - if ((_flags & Flags.AccessTime) != 0) - { - int iTime = helperStream.ReadLEInt(); - - _lastAccessTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + - new TimeSpan(0, 0, 0, iTime, 0); - } - - if ((_flags & Flags.CreateTime) != 0) - { - int iTime = helperStream.ReadLEInt(); - - _createTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + - new TimeSpan(0, 0, 0, iTime, 0); - } - } - } - - /// - /// Get the binary data representing this instance. - /// - /// The raw binary data representing this instance. - public byte[] GetData() - { - using (MemoryStream ms = new MemoryStream()) - using (ZipHelperStream helperStream = new ZipHelperStream(ms)) - { - helperStream.IsStreamOwner = false; - helperStream.WriteByte((byte)_flags); // Flags - if ((_flags & Flags.ModificationTime) != 0) - { - TimeSpan span = _modificationTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - var seconds = (int)span.TotalSeconds; - helperStream.WriteLEInt(seconds); - } - if ((_flags & Flags.AccessTime) != 0) - { - TimeSpan span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - var seconds = (int)span.TotalSeconds; - helperStream.WriteLEInt(seconds); - } - if ((_flags & Flags.CreateTime) != 0) - { - TimeSpan span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - var seconds = (int)span.TotalSeconds; - helperStream.WriteLEInt(seconds); - } - return ms.ToArray(); - } - } - - #endregion ITaggedData Members - - /// - /// Test a value to see if is valid and can be represented here. - /// - /// The value to test. - /// Returns true if the value is valid and can be represented; false if not. - /// The standard Unix time is a signed integer data type, directly encoding the Unix time number, - /// which is the number of seconds since 1970-01-01. - /// Being 32 bits means the values here cover a range of about 136 years. - /// The minimum representable time is 1901-12-13 20:45:52, - /// and the maximum representable time is 2038-01-19 03:14:07. - /// - public static bool IsValidValue(DateTime value) - { - return ((value >= new DateTime(1901, 12, 13, 20, 45, 52)) || - (value <= new DateTime(2038, 1, 19, 03, 14, 07))); - } - - /// - /// Get /set the Modification Time - /// - /// - /// - public DateTime ModificationTime - { - get { return _modificationTime; } - set - { - if (!IsValidValue(value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _flags |= Flags.ModificationTime; - _modificationTime = value; - } - } - - /// - /// Get / set the Access Time - /// - /// - /// - public DateTime AccessTime - { - get { return _lastAccessTime; } - set - { - if (!IsValidValue(value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _flags |= Flags.AccessTime; - _lastAccessTime = value; - } - } - - /// - /// Get / Set the Create Time - /// - /// - /// - public DateTime CreateTime - { - get { return _createTime; } - set - { - if (!IsValidValue(value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _flags |= Flags.CreateTime; - _createTime = value; - } - } - - /// - /// Get/set the values to include. - /// - public Flags Include - { - get { return _flags; } - set { _flags = value; } - } - - #region Instance Fields - - private Flags _flags; - private DateTime _modificationTime = new DateTime(1970, 1, 1); - private DateTime _lastAccessTime = new DateTime(1970, 1, 1); - private DateTime _createTime = new DateTime(1970, 1, 1); - - #endregion Instance Fields - } - - /// - /// Class handling NT date time values. - /// - public class NTTaggedData : ITaggedData - { - /// - /// Get the ID for this tagged data value. - /// - public short TagID - { - get { return 10; } - } - - /// - /// Set the data from the raw values provided. - /// - /// The raw data to extract values from. - /// The index to start extracting values from. - /// The number of bytes available. - public void SetData(byte[] data, int index, int count) - { - using (MemoryStream ms = new MemoryStream(data, index, count, false)) - using (ZipHelperStream helperStream = new ZipHelperStream(ms)) - { - helperStream.ReadLEInt(); // Reserved - while (helperStream.Position < helperStream.Length) - { - int ntfsTag = helperStream.ReadLEShort(); - int ntfsLength = helperStream.ReadLEShort(); - if (ntfsTag == 1) - { - if (ntfsLength >= 24) - { - long lastModificationTicks = helperStream.ReadLELong(); - _lastModificationTime = DateTime.FromFileTimeUtc(lastModificationTicks); - - long lastAccessTicks = helperStream.ReadLELong(); - _lastAccessTime = DateTime.FromFileTimeUtc(lastAccessTicks); - - long createTimeTicks = helperStream.ReadLELong(); - _createTime = DateTime.FromFileTimeUtc(createTimeTicks); - } - break; - } - else - { - // An unknown NTFS tag so simply skip it. - helperStream.Seek(ntfsLength, SeekOrigin.Current); - } - } - } - } - - /// - /// Get the binary data representing this instance. - /// - /// The raw binary data representing this instance. - public byte[] GetData() - { - using (MemoryStream ms = new MemoryStream()) - using (ZipHelperStream helperStream = new ZipHelperStream(ms)) - { - helperStream.IsStreamOwner = false; - helperStream.WriteLEInt(0); // Reserved - helperStream.WriteLEShort(1); // Tag - helperStream.WriteLEShort(24); // Length = 3 x 8. - helperStream.WriteLELong(_lastModificationTime.ToFileTimeUtc()); - helperStream.WriteLELong(_lastAccessTime.ToFileTimeUtc()); - helperStream.WriteLELong(_createTime.ToFileTimeUtc()); - return ms.ToArray(); - } - } - - /// - /// Test a valuie to see if is valid and can be represented here. - /// - /// The value to test. - /// Returns true if the value is valid and can be represented; false if not. - /// - /// NTFS filetimes are 64-bit unsigned integers, stored in Intel - /// (least significant byte first) byte order. They determine the - /// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch", - /// which is "01-Jan-1601 00:00:00 UTC". 28 May 60056 is the upper limit - /// - public static bool IsValidValue(DateTime value) - { - bool result = true; - try - { - value.ToFileTimeUtc(); - } - catch - { - result = false; - } - return result; - } - - /// - /// Get/set the last modification time. - /// - public DateTime LastModificationTime - { - get { return _lastModificationTime; } - set - { - if (!IsValidValue(value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - _lastModificationTime = value; - } - } - - /// - /// Get /set the create time - /// - public DateTime CreateTime - { - get { return _createTime; } - set - { - if (!IsValidValue(value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - _createTime = value; - } - } - - /// - /// Get /set the last access time. - /// - public DateTime LastAccessTime - { - get { return _lastAccessTime; } - set - { - if (!IsValidValue(value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - _lastAccessTime = value; - } - } - - #region Instance Fields - - private DateTime _lastAccessTime = DateTime.FromFileTimeUtc(0); - private DateTime _lastModificationTime = DateTime.FromFileTimeUtc(0); - private DateTime _createTime = DateTime.FromFileTimeUtc(0); - - #endregion Instance Fields - } - - /// - /// A factory that creates tagged data instances. - /// - internal interface ITaggedDataFactory - { - /// - /// Get data for a specific tag value. - /// - /// The tag ID to find. - /// The data to search. - /// The offset to begin extracting data from. - /// The number of bytes to extract. - /// The located value found, or null if not found. - ITaggedData Create(short tag, byte[] data, int offset, int count); - } - - /// - /// - /// A class to handle the extra data field for Zip entries - /// - /// - /// Extra data contains 0 or more values each prefixed by a header tag and length. - /// They contain zero or more bytes of actual data. - /// The data is held internally using a copy on write strategy. This is more efficient but - /// means that for extra data created by passing in data can have the values modified by the caller - /// in some circumstances. - /// - sealed public class ZipExtraData : IDisposable - { - #region Constructors - - /// - /// Initialise a default instance. - /// - public ZipExtraData() - { - Clear(); - } - - /// - /// Initialise with known extra data. - /// - /// The extra data. - public ZipExtraData(byte[] data) - { - if (data == null) - { - _data = Empty.Array(); - } - else - { - _data = data; - } - } - - #endregion Constructors - - /// - /// Get the raw extra data value - /// - /// Returns the raw byte[] extra data this instance represents. - public byte[] GetEntryData() - { - if (Length > ushort.MaxValue) - { - throw new ZipException("Data exceeds maximum length"); - } - - return (byte[])_data.Clone(); - } - - /// - /// Clear the stored data. - /// - public void Clear() - { - if ((_data == null) || (_data.Length != 0)) - { - _data = Empty.Array(); - } - } - - /// - /// Gets the current extra data length. - /// - public int Length - { - get { return _data.Length; } - } - - /// - /// Get a read-only for the associated tag. - /// - /// The tag to locate data for. - /// Returns a containing tag data or null if no tag was found. - public Stream GetStreamForTag(int tag) - { - Stream result = null; - if (Find(tag)) - { - result = new MemoryStream(_data, _index, _readValueLength, false); - } - return result; - } - - /// - /// Get the tagged data for a tag. - /// - /// The tag to search for. - /// Returns a tagged value or null if none found. - public T GetData() - where T : class, ITaggedData, new() - { - T result = new T(); - if (Find(result.TagID)) - { - result.SetData(_data, _readValueStart, _readValueLength); - return result; - } - else return null; - } - - /// - /// Get the length of the last value found by - /// - /// This is only valid if has previously returned true. - public int ValueLength - { - get { return _readValueLength; } - } - - /// - /// Get the index for the current read value. - /// - /// This is only valid if has previously returned true. - /// Initially the result will be the index of the first byte of actual data. The value is updated after calls to - /// , and . - public int CurrentReadIndex - { - get { return _index; } - } - - /// - /// Get the number of bytes remaining to be read for the current value; - /// - public int UnreadCount - { - get - { - if ((_readValueStart > _data.Length) || - (_readValueStart < 4)) - { - throw new ZipException("Find must be called before calling a Read method"); - } - - return _readValueStart + _readValueLength - _index; - } - } - - /// - /// Find an extra data value - /// - /// The identifier for the value to find. - /// Returns true if the value was found; false otherwise. - public bool Find(int headerID) - { - _readValueStart = _data.Length; - _readValueLength = 0; - _index = 0; - - int localLength = _readValueStart; - int localTag = headerID - 1; - - // Trailing bytes that cant make up an entry (as there arent enough - // bytes for a tag and length) are ignored! - while ((localTag != headerID) && (_index < _data.Length - 3)) - { - localTag = ReadShortInternal(); - localLength = ReadShortInternal(); - if (localTag != headerID) - { - _index += localLength; - } - } - - bool result = (localTag == headerID) && ((_index + localLength) <= _data.Length); - - if (result) - { - _readValueStart = _index; - _readValueLength = localLength; - } - - return result; - } - - /// - /// Add a new entry to extra data. - /// - /// The value to add. - public void AddEntry(ITaggedData taggedData) - { - if (taggedData == null) - { - throw new ArgumentNullException(nameof(taggedData)); - } - AddEntry(taggedData.TagID, taggedData.GetData()); - } - - /// - /// Add a new entry to extra data - /// - /// The ID for this entry. - /// The data to add. - /// If the ID already exists its contents are replaced. - public void AddEntry(int headerID, byte[] fieldData) - { - if ((headerID > ushort.MaxValue) || (headerID < 0)) - { - throw new ArgumentOutOfRangeException(nameof(headerID)); - } - - int addLength = (fieldData == null) ? 0 : fieldData.Length; - - if (addLength > ushort.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length"); - } - - // Test for new length before adjusting data. - int newLength = _data.Length + addLength + 4; - - if (Find(headerID)) - { - newLength -= (ValueLength + 4); - } - - if (newLength > ushort.MaxValue) - { - throw new ZipException("Data exceeds maximum length"); - } - - Delete(headerID); - - byte[] newData = new byte[newLength]; - _data.CopyTo(newData, 0); - int index = _data.Length; - _data = newData; - SetShort(ref index, headerID); - SetShort(ref index, addLength); - if (fieldData != null) - { - fieldData.CopyTo(newData, index); - } - } - - /// - /// Start adding a new entry. - /// - /// Add data using , , , or . - /// The new entry is completed and actually added by calling - /// - public void StartNewEntry() - { - _newEntry = new MemoryStream(); - } - - /// - /// Add entry data added since using the ID passed. - /// - /// The identifier to use for this entry. - public void AddNewEntry(int headerID) - { - byte[] newData = _newEntry.ToArray(); - _newEntry = null; - AddEntry(headerID, newData); - } - - /// - /// Add a byte of data to the pending new entry. - /// - /// The byte to add. - /// - public void AddData(byte data) - { - _newEntry.WriteByte(data); - } - - /// - /// Add data to a pending new entry. - /// - /// The data to add. - /// - public void AddData(byte[] data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - _newEntry.Write(data, 0, data.Length); - } - - /// - /// Add a short value in little endian order to the pending new entry. - /// - /// The data to add. - /// - public void AddLeShort(int toAdd) - { - unchecked - { - _newEntry.WriteByte((byte)toAdd); - _newEntry.WriteByte((byte)(toAdd >> 8)); - } - } - - /// - /// Add an integer value in little endian order to the pending new entry. - /// - /// The data to add. - /// - public void AddLeInt(int toAdd) - { - unchecked - { - AddLeShort((short)toAdd); - AddLeShort((short)(toAdd >> 16)); - } - } - - /// - /// Add a long value in little endian order to the pending new entry. - /// - /// The data to add. - /// - public void AddLeLong(long toAdd) - { - unchecked - { - AddLeInt((int)(toAdd & 0xffffffff)); - AddLeInt((int)(toAdd >> 32)); - } - } - - /// - /// Delete an extra data field. - /// - /// The identifier of the field to delete. - /// Returns true if the field was found and deleted. - public bool Delete(int headerID) - { - bool result = false; - - if (Find(headerID)) - { - result = true; - int trueStart = _readValueStart - 4; - - byte[] newData = new byte[_data.Length - (ValueLength + 4)]; - Array.Copy(_data, 0, newData, 0, trueStart); - - int trueEnd = trueStart + ValueLength + 4; - Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd); - _data = newData; - } - return result; - } - - #region Reading Support - - /// - /// Read a long in little endian form from the last found data value - /// - /// Returns the long value read. - public long ReadLong() - { - ReadCheck(8); - return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32); - } - - /// - /// Read an integer in little endian form from the last found data value. - /// - /// Returns the integer read. - public int ReadInt() - { - ReadCheck(4); - - int result = _data[_index] + (_data[_index + 1] << 8) + - (_data[_index + 2] << 16) + (_data[_index + 3] << 24); - _index += 4; - return result; - } - - /// - /// Read a short value in little endian form from the last found data value. - /// - /// Returns the short value read. - public int ReadShort() - { - ReadCheck(2); - int result = _data[_index] + (_data[_index + 1] << 8); - _index += 2; - return result; - } - - /// - /// Read a byte from an extra data - /// - /// The byte value read or -1 if the end of data has been reached. - public int ReadByte() - { - int result = -1; - if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) - { - result = _data[_index]; - _index += 1; - } - return result; - } - - /// - /// Skip data during reading. - /// - /// The number of bytes to skip. - public void Skip(int amount) - { - ReadCheck(amount); - _index += amount; - } - - private void ReadCheck(int length) - { - if ((_readValueStart > _data.Length) || - (_readValueStart < 4)) - { - throw new ZipException("Find must be called before calling a Read method"); - } - - if (_index > _readValueStart + _readValueLength - length) - { - throw new ZipException("End of extra data"); - } - - if (_index + length < 4) - { - throw new ZipException("Cannot read before start of tag"); - } - } - - /// - /// Internal form of that reads data at any location. - /// - /// Returns the short value read. - private int ReadShortInternal() - { - if (_index > _data.Length - 2) - { - throw new ZipException("End of extra data"); - } - - int result = _data[_index] + (_data[_index + 1] << 8); - _index += 2; - return result; - } - - private void SetShort(ref int index, int source) - { - _data[index] = (byte)source; - _data[index + 1] = (byte)(source >> 8); - index += 2; - } - - #endregion Reading Support - - #region IDisposable Members - - /// - /// Dispose of this instance. - /// - public void Dispose() - { - if (_newEntry != null) - { - _newEntry.Dispose(); - } - } - - #endregion IDisposable Members - - #region Instance Fields - - private int _index; - private int _readValueStart; - private int _readValueLength; - - private MemoryStream _newEntry; - private byte[] _data; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Initialise a default instance. + /// + public ZipExtraData() + { + Clear(); + } + + /// + /// Initialise with known extra data. + /// + /// The extra data. + public ZipExtraData(byte[] data) + { + _data = data == null ? Empty.Array() : data; + } + + #endregion Constructors + + /// + /// Get the raw extra data value + /// + /// Returns the raw byte[] extra data this instance represents. + public byte[] GetEntryData() + { + return Length > ushort.MaxValue ? throw new ZipException("Data exceeds maximum length") : (byte[])_data.Clone(); + } + + /// + /// Clear the stored data. + /// + public void Clear() + { + if ((_data == null) || (_data.Length != 0)) + { + _data = Empty.Array(); + } + } + + /// + /// Gets the current extra data length. + /// + public int Length + { + get { return _data.Length; } + } + + /// + /// Get a read-only for the associated tag. + /// + /// The tag to locate data for. + /// Returns a containing tag data or null if no tag was found. + public Stream GetStreamForTag(int tag) + { + Stream result = null; + if (Find(tag)) + { + result = new MemoryStream(_data, _index, _readValueLength, false); + } + return result; + } + + /// + /// Get the tagged data for a tag. + /// + /// The tag to search for. + /// Returns a tagged value or null if none found. + public T GetData() + where T : class, ITaggedData, new() + { + var result = new T(); + if (Find(result.TagID)) + { + result.SetData(_data, _readValueStart, _readValueLength); + return result; + } + else + return null; + } + + /// + /// Get the length of the last value found by + /// + /// This is only valid if has previously returned true. + public int ValueLength + { + get { return _readValueLength; } + } + + /// + /// Get the index for the current read value. + /// + /// This is only valid if has previously returned true. + /// Initially the result will be the index of the first byte of actual data. The value is updated after calls to + /// , and . + public int CurrentReadIndex + { + get { return _index; } + } + + /// + /// Get the number of bytes remaining to be read for the current value; + /// + public int UnreadCount + { + get + { + return (_readValueStart > _data.Length) || + (_readValueStart < 4) + ? throw new ZipException("Find must be called before calling a Read method") + : _readValueStart + _readValueLength - _index; + } + } + + /// + /// Find an extra data value + /// + /// The identifier for the value to find. + /// Returns true if the value was found; false otherwise. + public bool Find(int headerID) + { + _readValueStart = _data.Length; + _readValueLength = 0; + _index = 0; + + var localLength = _readValueStart; + var localTag = headerID - 1; + + // Trailing bytes that cant make up an entry (as there arent enough + // bytes for a tag and length) are ignored! + while ((localTag != headerID) && (_index < _data.Length - 3)) + { + localTag = ReadShortInternal(); + localLength = ReadShortInternal(); + if (localTag != headerID) + { + _index += localLength; + } + } + + var result = (localTag == headerID) && ((_index + localLength) <= _data.Length); + + if (result) + { + _readValueStart = _index; + _readValueLength = localLength; + } + + return result; + } + + /// + /// Add a new entry to extra data. + /// + /// The value to add. + public void AddEntry(ITaggedData taggedData) + { + if (taggedData == null) + { + throw new ArgumentNullException(nameof(taggedData)); + } + AddEntry(taggedData.TagID, taggedData.GetData()); + } + + /// + /// Add a new entry to extra data + /// + /// The ID for this entry. + /// The data to add. + /// If the ID already exists its contents are replaced. + public void AddEntry(int headerID, byte[] fieldData) + { + if (headerID is > ushort.MaxValue or < 0) + { + throw new ArgumentOutOfRangeException(nameof(headerID)); + } + + var addLength = (fieldData == null) ? 0 : fieldData.Length; + + if (addLength > ushort.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(fieldData), "exceeds maximum length"); + } + + // Test for new length before adjusting data. + var newLength = _data.Length + addLength + 4; + + if (Find(headerID)) + { + newLength -= ValueLength + 4; + } + + if (newLength > ushort.MaxValue) + { + throw new ZipException("Data exceeds maximum length"); + } + + Delete(headerID); + + var newData = new byte[newLength]; + _data.CopyTo(newData, 0); + var index = _data.Length; + _data = newData; + SetShort(ref index, headerID); + SetShort(ref index, addLength); + fieldData?.CopyTo(newData, index); + } + + /// + /// Start adding a new entry. + /// + /// Add data using , , , or . + /// The new entry is completed and actually added by calling + /// + public void StartNewEntry() + { + _newEntry = new MemoryStream(); + } + + /// + /// Add entry data added since using the ID passed. + /// + /// The identifier to use for this entry. + public void AddNewEntry(int headerID) + { + var newData = _newEntry.ToArray(); + _newEntry = null; + AddEntry(headerID, newData); + } + + /// + /// Add a byte of data to the pending new entry. + /// + /// The byte to add. + /// + public void AddData(byte data) + { + _newEntry.WriteByte(data); + } + + /// + /// Add data to a pending new entry. + /// + /// The data to add. + /// + public void AddData(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + _newEntry.Write(data, 0, data.Length); + } + + /// + /// Add a short value in little endian order to the pending new entry. + /// + /// The data to add. + /// + public void AddLeShort(int toAdd) + { + unchecked + { + _newEntry.WriteByte((byte)toAdd); + _newEntry.WriteByte((byte)(toAdd >> 8)); + } + } + + /// + /// Add an integer value in little endian order to the pending new entry. + /// + /// The data to add. + /// + public void AddLeInt(int toAdd) + { + unchecked + { + AddLeShort((short)toAdd); + AddLeShort((short)(toAdd >> 16)); + } + } + + /// + /// Add a long value in little endian order to the pending new entry. + /// + /// The data to add. + /// + public void AddLeLong(long toAdd) + { + unchecked + { + AddLeInt((int)(toAdd & 0xffffffff)); + AddLeInt((int)(toAdd >> 32)); + } + } + + /// + /// Delete an extra data field. + /// + /// The identifier of the field to delete. + /// Returns true if the field was found and deleted. + public bool Delete(int headerID) + { + var result = false; + + if (Find(headerID)) + { + result = true; + var trueStart = _readValueStart - 4; + + var newData = new byte[_data.Length - (ValueLength + 4)]; + Array.Copy(_data, 0, newData, 0, trueStart); + + var trueEnd = trueStart + ValueLength + 4; + Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd); + _data = newData; + } + return result; + } + + #region Reading Support + + /// + /// Read a long in little endian form from the last found data value + /// + /// Returns the long value read. + public long ReadLong() + { + ReadCheck(8); + return (ReadInt() & 0xffffffff) | (((long)ReadInt()) << 32); + } + + /// + /// Read an integer in little endian form from the last found data value. + /// + /// Returns the integer read. + public int ReadInt() + { + ReadCheck(4); + + var result = _data[_index] + (_data[_index + 1] << 8) + + (_data[_index + 2] << 16) + (_data[_index + 3] << 24); + _index += 4; + return result; + } + + /// + /// Read a short value in little endian form from the last found data value. + /// + /// Returns the short value read. + public int ReadShort() + { + ReadCheck(2); + var result = _data[_index] + (_data[_index + 1] << 8); + _index += 2; + return result; + } + + /// + /// Read a byte from an extra data + /// + /// The byte value read or -1 if the end of data has been reached. + public int ReadByte() + { + var result = -1; + if ((_index < _data.Length) && (_readValueStart + _readValueLength > _index)) + { + result = _data[_index]; + _index += 1; + } + return result; + } + + /// + /// Skip data during reading. + /// + /// The number of bytes to skip. + public void Skip(int amount) + { + ReadCheck(amount); + _index += amount; + } + + private void ReadCheck(int length) + { + if ((_readValueStart > _data.Length) || + (_readValueStart < 4)) + { + throw new ZipException("Find must be called before calling a Read method"); + } + + if (_index > _readValueStart + _readValueLength - length) + { + throw new ZipException("End of extra data"); + } + + if (_index + length < 4) + { + throw new ZipException("Cannot read before start of tag"); + } + } + + /// + /// Internal form of that reads data at any location. + /// + /// Returns the short value read. + private int ReadShortInternal() + { + if (_index > _data.Length - 2) + { + throw new ZipException("End of extra data"); + } + + var result = _data[_index] + (_data[_index + 1] << 8); + _index += 2; + return result; + } + + private void SetShort(ref int index, int source) + { + _data[index] = (byte)source; + _data[index + 1] = (byte)(source >> 8); + index += 2; + } + + #endregion Reading Support + + #region IDisposable Members + + /// + /// Dispose of this instance. + /// + public void Dispose() + { + _newEntry?.Dispose(); + } + + #endregion IDisposable Members + + #region Instance Fields + + private int _index; + private int _readValueStart; + private int _readValueLength; + + private MemoryStream _newEntry; + private byte[] _data; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs index 0bfad475a..b10610cfa 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs @@ -10,4902 +10,4823 @@ using System.Security.Cryptography; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +#region Keys Required Event Args + +/// +/// Arguments used with KeysRequiredEvent +/// +public class KeysRequiredEventArgs : EventArgs +{ + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The name of the file for which keys are required. + public KeysRequiredEventArgs(string name) + { + fileName = name; + } + + /// + /// Initialise a new instance of + /// + /// The name of the file for which keys are required. + /// The current key value. + public KeysRequiredEventArgs(string name, byte[] keyValue) + { + fileName = name; + key = keyValue; + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the name of the file for which keys are required. + /// + public string FileName + { + get { return fileName; } + } + + /// + /// Gets or sets the key value + /// + public byte[] Key + { + get { return key; } + set { key = value; } + } + + #endregion Properties + + #region Instance Fields + + private readonly string fileName; + private byte[] key; + + #endregion Instance Fields +} + +#endregion Keys Required Event Args + +#region Test Definitions + +/// +/// The strategy to apply to testing. +/// +public enum TestStrategy +{ + /// + /// Find the first error only. + /// + FindFirstError, + + /// + /// Find all possible errors. + /// + FindAllErrors, +} + +/// +/// The operation in progress reported by a during testing. +/// +/// TestArchive +public enum TestOperation +{ + /// + /// Setting up testing. + /// + Initialising, + + /// + /// Testing an individual entries header + /// + EntryHeader, + + /// + /// Testing an individual entries data + /// + EntryData, + + /// + /// Testing an individual entry has completed. + /// + EntryComplete, + + /// + /// Running miscellaneous tests + /// + MiscellaneousTests, + + /// + /// Testing is complete + /// + Complete, +} + +/// +/// Status returned by during testing. +/// +/// TestArchive +public class TestStatus +{ + #region Constructors + + /// + /// Initialise a new instance of + /// + /// The this status applies to. + public TestStatus(ZipFile file) + { + file_ = file; + } + + #endregion Constructors + + #region Properties + + /// + /// Get the current in progress. + /// + public TestOperation Operation + { + get { return operation_; } + } + + /// + /// Get the this status is applicable to. + /// + public ZipFile File + { + get { return file_; } + } + + /// + /// Get the current/last entry tested. + /// + public ZipEntry Entry + { + get { return entry_; } + } + + /// + /// Get the number of errors detected so far. + /// + public int ErrorCount + { + get { return errorCount_; } + } + + /// + /// Get the number of bytes tested so far for the current entry. + /// + public long BytesTested + { + get { return bytesTested_; } + } + + /// + /// Get a value indicating whether the last entry test was valid. + /// + public bool EntryValid + { + get { return entryValid_; } + } + + #endregion Properties + + #region Internal API + + internal void AddError() + { + errorCount_++; + entryValid_ = false; + } + + internal void SetOperation(TestOperation operation) + { + operation_ = operation; + } + + internal void SetEntry(ZipEntry entry) + { + entry_ = entry; + entryValid_ = true; + bytesTested_ = 0; + } + + internal void SetBytesTested(long value) + { + bytesTested_ = value; + } + + #endregion Internal API + + #region Instance Fields + + private readonly ZipFile file_; + private ZipEntry entry_; + private bool entryValid_; + private int errorCount_; + private long bytesTested_; + private TestOperation operation_; + + #endregion Instance Fields +} + +/// +/// Delegate invoked during testing if supplied indicating current progress and status. +/// +/// If the message is non-null an error has occured. If the message is null +/// the operation as found in status has started. +public delegate void ZipTestResultHandler(TestStatus status, string message); + +#endregion Test Definitions + +#region Update Definitions + +/// +/// The possible ways of applying updates to an archive. +/// +public enum FileUpdateMode +{ + /// + /// Perform all updates on temporary files ensuring that the original file is saved. + /// + Safe, + + /// + /// Update the archive directly, which is faster but less safe. + /// + Direct, +} + +#endregion Update Definitions + +#region ZipFile Class + +/// +/// This class represents a Zip archive. You can ask for the contained +/// entries, or get an input stream for a file entry. The entry is +/// automatically decompressed. +/// +/// You can also update the archive adding or deleting entries. +/// +/// This class is thread safe for input: You can open input streams for arbitrary +/// entries in different threads. +///
+///
Author of the original java version : Jochen Hoenicke +///
+/// +/// +/// using System; +/// using System.Text; +/// using System.Collections; +/// using System.IO; +/// +/// using MelonLoader.ICSharpCode.SharpZipLib.Zip; +/// +/// class MainClass +/// { +/// static public void Main(string[] args) +/// { +/// using (ZipFile zFile = new ZipFile(args[0])) { +/// Console.WriteLine("Listing of : " + zFile.Name); +/// Console.WriteLine(""); +/// Console.WriteLine("Raw Size Size Date Time Name"); +/// Console.WriteLine("-------- -------- -------- ------ ---------"); +/// foreach (ZipEntry e in zFile) { +/// if ( e.IsFile ) { +/// DateTime d = e.DateTime; +/// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, +/// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), +/// e.Name); +/// } +/// } +/// } +/// } +/// } +/// +/// +public class ZipFile : IEnumerable, IDisposable +{ + #region KeyHandling + + /// + /// Delegate for handling keys/password setting during compression/decompression. + /// + public delegate void KeysRequiredEventHandler( + object sender, + KeysRequiredEventArgs e + ); + + /// + /// Event handler for handling encryption keys. + /// + public KeysRequiredEventHandler KeysRequired; + + /// + /// Handles getting of encryption keys when required. + /// + /// The file for which encryption keys are required. + private void OnKeysRequired(string fileName) + { + if (KeysRequired != null) + { + var krea = new KeysRequiredEventArgs(fileName, key); + KeysRequired(this, krea); + key = krea.Key; + } + } + + /// + /// Get/set the encryption key value. + /// + private byte[] Key + { + get { return key; } + set { key = value; } + } + + /// + /// Password to be used for encrypting/decrypting files. + /// + /// Set to null if no password is required. + public string Password + { + set + { + key = string.IsNullOrEmpty(value) ? null : PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(value)); + + rawPassword_ = value; + } + } + + /// + /// Get a value indicating whether encryption keys are currently available. + /// + private bool HaveKeys + { + get { return key != null; } + } + + #endregion KeyHandling + + #region Constructors + + /// + /// Opens a Zip file with the given name for reading. + /// + /// The name of the file to open. + /// The argument supplied is null. + /// + /// An i/o error occurs + /// + /// + /// The file doesn't contain a valid zip archive. + /// + public ZipFile(string name) + { + name_ = name ?? throw new ArgumentNullException(nameof(name)); + + baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); + isStreamOwner = true; + + try + { + ReadEntries(); + } + catch + { + DisposeInternal(true); + throw; + } + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// The supplied argument is null. + /// + /// An i/o error occurs. + /// + /// + /// The file doesn't contain a valid zip archive. + /// + public ZipFile(FileStream file) : + this(file, false) + { + + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// true to leave the file open when the ZipFile is disposed, false to dispose of it + /// The supplied argument is null. + /// + /// An i/o error occurs. + /// + /// + /// The file doesn't contain a valid zip archive. + /// + public ZipFile(FileStream file, bool leaveOpen) + { + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + + if (!file.CanSeek) + { + throw new ArgumentException("Stream is not seekable", nameof(file)); + } + + baseStream_ = file; + name_ = file.Name; + isStreamOwner = !leaveOpen; + + try + { + ReadEntries(); + } + catch + { + DisposeInternal(true); + throw; + } + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// + /// An i/o error occurs + /// + /// + /// The stream doesn't contain a valid zip archive.
+ ///
+ /// + /// The stream doesnt support seeking. + /// + /// + /// The stream argument is null. + /// + public ZipFile(Stream stream) : + this(stream, false) + { + + } + + /// + /// Opens a Zip file reading the given . + /// + /// The to read archive data from. + /// true to leave the stream open when the ZipFile is disposed, false to dispose of it + /// + /// An i/o error occurs + /// + /// + /// The stream doesn't contain a valid zip archive.
+ ///
+ /// + /// The stream doesnt support seeking. + /// + /// + /// The stream argument is null. + /// + public ZipFile(Stream stream, bool leaveOpen) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanSeek) + { + throw new ArgumentException("Stream is not seekable", nameof(stream)); + } + + baseStream_ = stream; + isStreamOwner = !leaveOpen; + + if (baseStream_.Length > 0) + { + try + { + ReadEntries(); + } + catch + { + DisposeInternal(true); + throw; + } + } + else + { + entries_ = Empty.Array(); + isNewArchive_ = true; + } + } + + /// + /// Initialises a default instance with no entries and no file storage. + /// + internal ZipFile() + { + entries_ = Empty.Array(); + isNewArchive_ = true; + } + + #endregion Constructors + + #region Destructors and Closing + + /// + /// Finalize this instance. + /// + ~ZipFile() + { + Dispose(false); + } + + /// + /// Closes the ZipFile. If the stream is owned then this also closes the underlying input stream. + /// Once closed, no further instance methods should be called. + /// + /// + /// An i/o error occurs. + /// + public void Close() + { + DisposeInternal(true); + GC.SuppressFinalize(this); + } + + #endregion Destructors and Closing + + #region Creators + + /// + /// Create a new whose data will be stored in a file. + /// + /// The name of the archive to create. + /// Returns the newly created + /// is null + public static ZipFile Create(string fileName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + var fs = File.Create(fileName); + + return new ZipFile + { + name_ = fileName, + baseStream_ = fs, + isStreamOwner = true + }; + } + + /// + /// Create a new whose data will be stored on a stream. + /// + /// The stream providing data storage. + /// Returns the newly created + /// is null + /// doesnt support writing. + public static ZipFile Create(Stream outStream) + { + if (outStream == null) + { + throw new ArgumentNullException(nameof(outStream)); + } + + if (!outStream.CanWrite) + { + throw new ArgumentException("Stream is not writeable", nameof(outStream)); + } + + if (!outStream.CanSeek) + { + throw new ArgumentException("Stream is not seekable", nameof(outStream)); + } + + var result = new ZipFile + { + baseStream_ = outStream + }; + return result; + } + + #endregion Creators + + #region Properties + + /// + /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. + /// If the flag is true then the stream will be closed when Close is called. + /// + /// + /// The default value is true in all cases. + /// + public bool IsStreamOwner + { + get { return isStreamOwner; } + set { isStreamOwner = value; } + } + + /// + /// Get a value indicating whether + /// this archive is embedded in another file or not. + /// + public bool IsEmbeddedArchive + { + // Not strictly correct in all circumstances currently + get { return offsetOfFirstEntry > 0; } + } + + /// + /// Get a value indicating that this archive is a new one. + /// + public bool IsNewArchive + { + get { return isNewArchive_; } + } + + /// + /// Gets the comment for the zip file. + /// + public string ZipFileComment + { + get { return comment_; } + } + + /// + /// Gets the name of this zip file. + /// + public string Name + { + get { return name_; } + } + + /// + /// Gets the number of entries in this zip file. + /// + /// + /// The Zip file has been closed. + /// + [Obsolete("Use the Count property instead")] + public int Size + { + get + { + return entries_.Length; + } + } + + /// + /// Get the number of entries contained in this . + /// + public long Count + { + get + { + return entries_.Length; + } + } + + /// + /// Indexer property for ZipEntries + /// + [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] + public ZipEntry this[int index] + { + get + { + return (ZipEntry)entries_[index].Clone(); + } + } + + #endregion Properties + + #region Input Handling + + /// + /// Gets an enumerator for the Zip entries in this Zip file. + /// + /// Returns an for this archive. + /// + /// The Zip file has been closed. + /// + public IEnumerator GetEnumerator() + { + return isDisposed_ ? throw new ObjectDisposedException("ZipFile") : (IEnumerator)new ZipEntryEnumerator(entries_); + } + + /// + /// Return the index of the entry with a matching name + /// + /// Entry name to find + /// If true the comparison is case insensitive + /// The index position of the matching entry or -1 if not found + /// + /// The Zip file has been closed. + /// + public int FindEntry(string name, bool ignoreCase) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + // TODO: This will be slow as the next ice age for huge archives! + for (var i = 0; i < entries_.Length; i++) + { + if (string.Compare(name, entries_[i].Name, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0) + { + return i; + } + } + return -1; + } + + /// + /// Searches for a zip entry in this archive with the given name. + /// String comparisons are case insensitive + /// + /// + /// The name to find. May contain directory components separated by slashes ('/'). + /// + /// + /// A clone of the zip entry, or null if no entry with that name exists. + /// + /// + /// The Zip file has been closed. + /// + public ZipEntry GetEntry(string name) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + var index = FindEntry(name, true); + return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; + } + + /// + /// Gets an input stream for reading the given zip entry data in an uncompressed form. + /// Normally the should be an entry returned by GetEntry(). + /// + /// The to obtain a data for + /// An input containing data for this + /// + /// The ZipFile has already been closed + /// + /// + /// The compression method for the entry is unknown + /// + /// + /// The entry is not found in the ZipFile + /// + public Stream GetInputStream(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + var index = entry.ZipFileIndex; + if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) + { + index = FindEntry(entry.Name, true); + if (index < 0) + { + throw new ZipException("Entry cannot be found"); + } + } + return GetInputStream(index); + } + + /// + /// Creates an input stream reading a zip entry + /// + /// The index of the entry to obtain an input stream for. + /// + /// An input containing data for this + /// + /// + /// The ZipFile has already been closed + /// + /// + /// The compression method for the entry is unknown + /// + /// + /// The entry is not found in the ZipFile + /// + public Stream GetInputStream(long entryIndex) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + var start = LocateEntry(entries_[entryIndex]); + var method = entries_[entryIndex].CompressionMethod; + Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); + + if (entries_[entryIndex].IsCrypted == true) + { + result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); + if (result == null) + { + throw new ZipException("Unable to decrypt this entry"); + } + } + + switch (method) + { + case CompressionMethod.Stored: + // read as is. + break; + + case CompressionMethod.Deflated: + // No need to worry about ownership and closing as underlying stream close does nothing. + result = new InflaterInputStream(result, new Inflater(true)); + break; + + case CompressionMethod.BZip2: + result = new BZip2.BZip2InputStream(result); + break; + + default: + throw new ZipException("Unsupported compression method " + method); + } + + return result; + } + + #endregion Input Handling + + #region Archive Testing + + /// + /// Test an archive for integrity/validity + /// + /// Perform low level data Crc check + /// true if all tests pass, false otherwise + /// Testing will terminate on the first error found. + public bool TestArchive(bool testData) + { + return TestArchive(testData, TestStrategy.FindFirstError, null); + } + + /// + /// Test an archive for integrity/validity + /// + /// Perform low level data Crc check + /// The to apply. + /// The handler to call during testing. + /// true if all tests pass, false otherwise + /// The object has already been closed. + public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + var status = new TestStatus(this); + + resultHandler?.Invoke(status, null); + + var test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; + + var testing = true; + + try + { + var entryIndex = 0; + + while (testing && (entryIndex < Count)) + { + if (resultHandler != null) + { + status.SetEntry(this[entryIndex]); + status.SetOperation(TestOperation.EntryHeader); + resultHandler(status, null); + } + + try + { + TestLocalHeader(this[entryIndex], test); + } + catch (ZipException ex) + { + status.AddError(); + + resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'"); + + testing &= strategy != TestStrategy.FindFirstError; + } + + if (testing && testData && this[entryIndex].IsFile) + { + // Don't check CRC for AES encrypted archives + var checkCRC = this[entryIndex].AESKeySize == 0; + + if (resultHandler != null) + { + status.SetOperation(TestOperation.EntryData); + resultHandler(status, null); + } + + var crc = new Crc32(); + + using (var entryStream = this.GetInputStream(this[entryIndex])) + { + var buffer = new byte[4096]; + long totalBytes = 0; + int bytesRead; + while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) + { + if (checkCRC) + { + crc.Update(new ArraySegment(buffer, 0, bytesRead)); + } + + if (resultHandler != null) + { + totalBytes += bytesRead; + status.SetBytesTested(totalBytes); + resultHandler(status, null); + } + } + } + + if (checkCRC && this[entryIndex].Crc != crc.Value) + { + status.AddError(); + + resultHandler?.Invoke(status, "CRC mismatch"); + + testing &= strategy != TestStrategy.FindFirstError; + } + + if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) + { + var helper = new ZipHelperStream(baseStream_); + var data = new DescriptorData(); + helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); + + if (checkCRC && this[entryIndex].Crc != data.Crc) + { + status.AddError(); + resultHandler?.Invoke(status, "Descriptor CRC mismatch"); + } + + if (this[entryIndex].CompressedSize != data.CompressedSize) + { + status.AddError(); + resultHandler?.Invoke(status, "Descriptor compressed size mismatch"); + } + + if (this[entryIndex].Size != data.Size) + { + status.AddError(); + resultHandler?.Invoke(status, "Descriptor size mismatch"); + } + } + } + + if (resultHandler != null) + { + status.SetOperation(TestOperation.EntryComplete); + resultHandler(status, null); + } + + entryIndex += 1; + } + + if (resultHandler != null) + { + status.SetOperation(TestOperation.MiscellaneousTests); + resultHandler(status, null); + } + + // TODO: the 'Corrina Johns' test where local headers are missing from + // the central directory. They are therefore invisible to many archivers. + } + catch (Exception ex) + { + status.AddError(); + + resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'"); + } + + if (resultHandler != null) + { + status.SetOperation(TestOperation.Complete); + status.SetEntry(null); + resultHandler(status, null); + } + + return status.ErrorCount == 0; + } + + [Flags] + private enum HeaderTest + { + Extract = 0x01, // Check that this header represents an entry whose data can be extracted + Header = 0x02, // Check that this header contents are valid + } + + /// + /// Test a local header against that provided from the central directory + /// + /// + /// The entry to test against + /// + /// The type of tests to carry out. + /// The offset of the entries data in the file + private long TestLocalHeader(ZipEntry entry, HeaderTest tests) + { + lock (baseStream_) + { + var testHeader = (tests & HeaderTest.Header) != 0; + var testData = (tests & HeaderTest.Extract) != 0; + + var entryAbsOffset = offsetOfFirstEntry + entry.Offset; + + baseStream_.Seek(entryAbsOffset, SeekOrigin.Begin); + var signature = (int)ReadLEUint(); + + if (signature != ZipConstants.LocalHeaderSignature) + { + throw new ZipException(string.Format("Wrong local header signature at 0x{0:x}, expected 0x{1:x8}, actual 0x{2:x8}", + entryAbsOffset, ZipConstants.LocalHeaderSignature, signature)); + } + + var extractVersion = (short)(ReadLEUshort() & 0x00ff); + var localFlags = (short)ReadLEUshort(); + var compressionMethod = (short)ReadLEUshort(); + var fileTime = (short)ReadLEUshort(); + var fileDate = (short)ReadLEUshort(); + var crcValue = ReadLEUint(); + long compressedSize = ReadLEUint(); + long size = ReadLEUint(); + int storedNameLength = ReadLEUshort(); + int extraDataLength = ReadLEUshort(); + + var nameData = new byte[storedNameLength]; + StreamUtils.ReadFully(baseStream_, nameData); + + var extraData = new byte[extraDataLength]; + StreamUtils.ReadFully(baseStream_, extraData); + + var localExtraData = new ZipExtraData(extraData); + + // Extra data / zip64 checks + if (localExtraData.Find(1)) + { + // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 + // and size or compressedSize = MaxValue, due to rogue creators. + + size = localExtraData.ReadLong(); + compressedSize = localExtraData.ReadLong(); + + if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) + { + // These may be valid if patched later + if ((size != -1) && (size != entry.Size)) + { + throw new ZipException("Size invalid for descriptor"); + } + + if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) + { + throw new ZipException("Compressed size invalid for descriptor"); + } + } + } + else + { + // No zip64 extra data but entry requires it. + if ((extractVersion >= ZipConstants.VersionZip64) && + (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) + { + throw new ZipException("Required Zip64 extended information missing"); + } + } + + if (testData) + { + if (entry.IsFile) + { + if (!entry.IsCompressionMethodSupported()) + { + throw new ZipException("Compression method not supported"); + } + + if (extractVersion is > ZipConstants.VersionMadeBy or > 20 and < ZipConstants.VersionZip64) + { + throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion)); + } + + if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0) + { + throw new ZipException("The library does not support the zip version required to extract this entry"); + } + } + } + + if (testHeader) + { + if (extractVersion is <= 63 and // Ignore later versions as we dont know about them.. + not 10 and + not 11 and + not 20 and + not 21 and + not 25 and + not 27 and + not 45 and + not 46 and + not 50 and + not 51 and + not 52 and + not 61 and + not 62 and + not 63) + { + throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion)); + } + + // Local entry flags dont have reserved bit set on. + if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0) + { + throw new ZipException("Reserved bit flags cannot be set."); + } + + // Encryption requires extract version >= 20 + if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) + { + throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); + } + + // Strong encryption requires encryption flag to be set and extract version >= 50. + if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) + { + if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) + { + throw new ZipException("Strong encryption flag set but encryption flag is not set"); + } + + if (extractVersion < 50) + { + throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); + } + } + + // Patched entries require extract version >= 27 + if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) + { + throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); + } + + // Central header flags match local entry flags. + if (localFlags != entry.Flags) + { + throw new ZipException("Central header/local header flags mismatch"); + } + + // Central header compression method matches local entry + if (entry.CompressionMethodForHeader != (CompressionMethod)compressionMethod) + { + throw new ZipException("Central header/local header compression method mismatch"); + } + + if (entry.Version != extractVersion) + { + throw new ZipException("Extract version mismatch"); + } + + // Strong encryption and extract version match + if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) + { + if (extractVersion < 62) + { + throw new ZipException("Strong encryption flag set but version not high enough"); + } + } + + if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) + { + if ((fileTime != 0) || (fileDate != 0)) + { + throw new ZipException("Header masked set but date/time values non-zero"); + } + } + + if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) + { + if (crcValue != (uint)entry.Crc) + { + throw new ZipException("Central header/local header crc mismatch"); + } + } + + // Crc valid for empty entry. + // This will also apply to streamed entries where size isnt known and the header cant be patched + if ((size == 0) && (compressedSize == 0)) + { + if (crcValue != 0) + { + throw new ZipException("Invalid CRC for empty entry"); + } + } + + // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings + // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably + if (entry.Name.Length > storedNameLength) + { + throw new ZipException("File name length mismatch"); + } + + // Name data has already been read convert it and compare. + var localName = ZipStrings.ConvertToStringExt(localFlags, nameData); + + // Central directory and local entry name match + if (localName != entry.Name) + { + throw new ZipException("Central header and local header file name mismatch"); + } + + // Directories have zero actual size but can have compressed size + if (entry.IsDirectory) + { + if (size > 0) + { + throw new ZipException("Directory cannot have size"); + } + + // There may be other cases where the compressed size can be greater than this? + // If so until details are known we will be strict. + if (entry.IsCrypted) + { + if (compressedSize > entry.EncryptionOverheadSize + 2) + { + throw new ZipException("Directory compressed size invalid"); + } + } + else if (compressedSize > 2) + { + // When not compressed the directory size can validly be 2 bytes + // if the true size wasn't known when data was originally being written. + // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes + throw new ZipException("Directory compressed size invalid"); + } + } + + if (!ZipNameTransform.IsValidName(localName, true)) + { + throw new ZipException("Name is invalid"); + } + } + + // Tests that apply to both data and header. + + // Size can be verified only if it is known in the local header. + // it will always be known in the central header. + if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || + ((size > 0 || compressedSize > 0) && entry.Size > 0)) + { + if ((size != 0) + && (size != entry.Size)) + { + throw new ZipException( + string.Format("Size mismatch between central header({0}) and local header({1})", + entry.Size, size)); + } + + if ((compressedSize != 0) + && compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1) + { + throw new ZipException( + string.Format("Compressed size mismatch between central header({0}) and local header({1})", + entry.CompressedSize, compressedSize)); + } + } + + var extraLength = storedNameLength + extraDataLength; + return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; + } + } + + #endregion Archive Testing + + #region Updating + + private const int DefaultBufferSize = 4096; + + /// + /// The kind of update to apply. + /// + private enum UpdateCommand + { + Copy, // Copy original file contents. + Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. + Add, // Add a new file to the archive. + } + + #region Properties + + /// + /// Get / set the to apply to names when updating. + /// + public INameTransform NameTransform + { + get + { + return updateEntryFactory_.NameTransform; + } + + set + { + updateEntryFactory_.NameTransform = value; + } + } + + /// + /// Get/set the used to generate values + /// during updates. + /// + public IEntryFactory EntryFactory + { + get + { + return updateEntryFactory_; + } + + set + { + updateEntryFactory_ = value == null ? new ZipEntryFactory() : value; + } + } + + /// + /// Get /set the buffer size to be used when updating this zip file. + /// + public int BufferSize + { + get { return bufferSize_; } + set + { + if (value < 1024) + { + throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); + } + + if (bufferSize_ != value) + { + bufferSize_ = value; + copyBuffer_ = null; + } + } + } + + /// + /// Get a value indicating an update has been started. + /// + public bool IsUpdating + { + get { return updates_ != null; } + } + + /// + /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. + /// + public UseZip64 UseZip64 + { + get { return useZip64_; } + set { useZip64_ = value; } + } + + #endregion Properties + + #region Immediate updating + + // TBD: Direct form of updating + // + // public void Update(IEntryMatcher deleteMatcher) + // { + // } + // + // public void Update(IScanner addScanner) + // { + // } + + #endregion Immediate updating + + #region Deferred Updating + + /// + /// Begin updating this archive. + /// + /// The archive storage for use during the update. + /// The data source to utilise during updating. + /// ZipFile has been closed. + /// One of the arguments provided is null + /// ZipFile has been closed. + public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + if (IsEmbeddedArchive) + { + throw new ZipException("Cannot update embedded/SFX archives"); + } + + archiveStorage_ = archiveStorage ?? throw new ArgumentNullException(nameof(archiveStorage)); + updateDataSource_ = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); + + // NOTE: the baseStream_ may not currently support writing or seeking. + + updateIndex_ = []; + + updates_ = new List(entries_.Length); + foreach (var entry in entries_) + { + var index = updates_.Count; + updates_.Add(new ZipUpdate(entry)); + updateIndex_.Add(entry.Name, index); + } + + // We must sort by offset before using offset's calculated sizes + updates_.Sort(new UpdateComparer()); + + var idx = 0; + foreach (var update in updates_) + { + //If last entry, there is no next entry offset to use + if (idx == updates_.Count - 1) + break; + + update.OffsetBasedSize = updates_[idx + 1].Entry.Offset - update.Entry.Offset; + idx++; + } + updateCount_ = updates_.Count; + + contentsEdited_ = false; + commentEdited_ = false; + newComment_ = null; + } + + /// + /// Begin updating to this archive. + /// + /// The storage to use during the update. + public void BeginUpdate(IArchiveStorage archiveStorage) + { + BeginUpdate(archiveStorage, new DynamicDiskDataSource()); + } + + /// + /// Begin updating this archive. + /// + /// + /// + /// + public void BeginUpdate() + { + if (Name == null) + { + BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); + } + else + { + BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); + } + } + + /// + /// Commit current updates, updating this archive. + /// + /// + /// + /// ZipFile has been closed. + public void CommitUpdate() + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + CheckUpdating(); + + try + { + updateIndex_.Clear(); + updateIndex_ = null; + + if (contentsEdited_) + { + RunUpdates(); + } + else if (commentEdited_) + { + UpdateCommentOnly(); + } + else + { + // Create an empty archive if none existed originally. + if (entries_.Length == 0) + { + var theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_); + using var zhs = new ZipHelperStream(baseStream_); + zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); + } + } + } + finally + { + PostUpdateCleanup(); + } + } + + /// + /// Abort updating leaving the archive unchanged. + /// + /// + /// + public void AbortUpdate() + { + PostUpdateCleanup(); + } + + /// + /// Set the file comment to be recorded when the current update is commited. + /// + /// The comment to record. + /// ZipFile has been closed. + public void SetComment(string comment) + { + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + CheckUpdating(); + + newComment_ = new ZipString(comment); + + if (newComment_.RawLength > 0xffff) + { + newComment_ = null; + throw new ZipException("Comment length exceeds maximum - 65535"); + } + + // We dont take account of the original and current comment appearing to be the same + // as encoding may be different. + commentEdited_ = true; + } + + #endregion Deferred Updating + + #region Adding Entries + + private void AddUpdate(ZipUpdate update) + { + contentsEdited_ = true; + + var index = FindExistingUpdate(update.Entry.Name, isEntryName: true); + + if (index >= 0) + { + if (updates_[index] == null) + { + updateCount_ += 1; + } + + // Direct replacement is faster than delete and add. + updates_[index] = update; + } + else + { + index = updates_.Count; + updates_.Add(update); + updateCount_ += 1; + updateIndex_.Add(update.Entry.Name, index); + } + } + + /// + /// Add a new entry to the archive. + /// + /// The name of the file to add. + /// The compression method to use. + /// Ensure Unicode text is used for name and comment for this entry. + /// Argument supplied is null. + /// ZipFile has been closed. + /// Compression method is not supported for creating entries. + public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + if (isDisposed_) + { + throw new ObjectDisposedException("ZipFile"); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + contentsEdited_ = true; + + var entry = EntryFactory.MakeFileEntry(fileName); + entry.IsUnicodeText = useUnicodeText; + entry.CompressionMethod = compressionMethod; + + AddUpdate(new ZipUpdate(fileName, entry)); + } + + /// + /// Add a new entry to the archive. + /// + /// The name of the file to add. + /// The compression method to use. + /// ZipFile has been closed. + /// Compression method is not supported for creating entries. + public void Add(string fileName, CompressionMethod compressionMethod) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + contentsEdited_ = true; + + var entry = EntryFactory.MakeFileEntry(fileName); + entry.CompressionMethod = compressionMethod; + AddUpdate(new ZipUpdate(fileName, entry)); + } + + /// + /// Add a file to the archive. + /// + /// The name of the file to add. + /// Argument supplied is null. + public void Add(string fileName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + CheckUpdating(); + AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); + } + + /// + /// Add a file to the archive. + /// + /// The name of the file to add. + /// The name to use for the on the Zip file created. + /// Argument supplied is null. + public void Add(string fileName, string entryName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckUpdating(); + AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); + } + + /// + /// Add a file entry with data. + /// + /// The source of the data for this entry. + /// The name to give to the entry. + public void Add(IStaticDataSource dataSource, string entryName) + { + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckUpdating(); + AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); + } + + /// + /// Add a file entry with data. + /// + /// The source of the data for this entry. + /// The name to give to the entry. + /// The compression method to use. + /// Compression method is not supported for creating entries. + public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) + { + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + + var entry = EntryFactory.MakeFileEntry(entryName, false); + entry.CompressionMethod = compressionMethod; + + AddUpdate(new ZipUpdate(dataSource, entry)); + } + + /// + /// Add a file entry with data. + /// + /// The source of the data for this entry. + /// The name to give to the entry. + /// The compression method to use. + /// Ensure Unicode text is used for name and comments for this entry. + /// Compression method is not supported for creating entries. + public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicodeText) + { + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + if (entryName == null) + { + throw new ArgumentNullException(nameof(entryName)); + } + + CheckSupportedCompressionMethod(compressionMethod); + CheckUpdating(); + + var entry = EntryFactory.MakeFileEntry(entryName, false); + entry.IsUnicodeText = useUnicodeText; + entry.CompressionMethod = compressionMethod; + + AddUpdate(new ZipUpdate(dataSource, entry)); + } + + /// + /// Add a that contains no data. + /// + /// The entry to add. + /// This can be used to add directories, volume labels, or empty file entries. + public void Add(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + CheckUpdating(); + + if ((entry.Size != 0) || (entry.CompressedSize != 0)) + { + throw new ZipException("Entry cannot have any data"); + } + + AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); + } + + /// + /// Add a with data. + /// + /// The source of the data for this entry. + /// The entry to add. + /// This can be used to add file entries with a custom data source. + /// + /// The encryption method specified in is unsupported. + /// + /// Compression method is not supported for creating entries. + public void Add(IStaticDataSource dataSource, ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + if (dataSource == null) + { + throw new ArgumentNullException(nameof(dataSource)); + } + + // We don't currently support adding entries with AES encryption, so throw + // up front instead of failing or falling back to ZipCrypto later on + if (entry.AESKeySize > 0) + { + throw new NotSupportedException("Creation of AES encrypted entries is not supported"); + } + + CheckSupportedCompressionMethod(entry.CompressionMethod); + CheckUpdating(); + + AddUpdate(new ZipUpdate(dataSource, entry)); + } + + /// + /// Add a directory entry to the archive. + /// + /// The directory to add. + public void AddDirectory(string directoryName) + { + if (directoryName == null) + { + throw new ArgumentNullException(nameof(directoryName)); + } + + CheckUpdating(); + + var dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); + AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); + } + + /// + /// Check if the specified compression method is supported for adding a new entry. + /// + /// The compression method for the new entry. + private static void CheckSupportedCompressionMethod(CompressionMethod compressionMethod) + { + if (compressionMethod is not CompressionMethod.Deflated and not CompressionMethod.Stored and not CompressionMethod.BZip2) + { + throw new NotImplementedException("Compression method not supported"); + } + } + + #endregion Adding Entries + + #region Modifying Entries + + /* Modify not yet ready for public consumption. + Direct modification of an entry should not overwrite original data before its read. + Safe mode is trivial in this sense. + public void Modify(ZipEntry original, ZipEntry updated) + { + if ( original == null ) { + throw new ArgumentNullException("original"); + } + if ( updated == null ) { + throw new ArgumentNullException("updated"); + } + CheckUpdating(); + contentsEdited_ = true; + updates_.Add(new ZipUpdate(original, updated)); + } + */ + + #endregion Modifying Entries + + #region Deleting Entries + + /// + /// Delete an entry by name + /// + /// The filename to delete + /// True if the entry was found and deleted; false otherwise. + public bool Delete(string fileName) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + CheckUpdating(); + + var index = FindExistingUpdate(fileName); + + bool result; + if ((index >= 0) && (updates_[index] != null)) + { + result = true; + contentsEdited_ = true; + updates_[index] = null; + updateCount_ -= 1; + } + else + { + throw new ZipException("Cannot find entry to delete"); + } + return result; + } + + /// + /// Delete a from the archive. + /// + /// The entry to delete. + public void Delete(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + CheckUpdating(); + + var index = FindExistingUpdate(entry); + if (index >= 0) + { + contentsEdited_ = true; + updates_[index] = null; + updateCount_ -= 1; + } + else + { + throw new ZipException("Cannot find entry to delete"); + } + } + + #endregion Deleting Entries + + #region Update Support + + #region Writing Values/Headers + + private void WriteLEShort(int value) + { + baseStream_.WriteByte((byte)(value & 0xff)); + baseStream_.WriteByte((byte)((value >> 8) & 0xff)); + } + + /// + /// Write an unsigned short in little endian byte order. + /// + private void WriteLEUshort(ushort value) + { + baseStream_.WriteByte((byte)(value & 0xff)); + baseStream_.WriteByte((byte)(value >> 8)); + } + + /// + /// Write an int in little endian byte order. + /// + private void WriteLEInt(int value) + { + WriteLEShort(value & 0xffff); + WriteLEShort(value >> 16); + } + + /// + /// Write an unsigned int in little endian byte order. + /// + private void WriteLEUint(uint value) + { + WriteLEUshort((ushort)(value & 0xffff)); + WriteLEUshort((ushort)(value >> 16)); + } + + /// + /// Write a long in little endian byte order. + /// + private void WriteLeLong(long value) + { + WriteLEInt((int)(value & 0xffffffff)); + WriteLEInt((int)(value >> 32)); + } + + private void WriteLEUlong(ulong value) + { + WriteLEUint((uint)(value & 0xffffffff)); + WriteLEUint((uint)(value >> 32)); + } + + private void WriteLocalEntryHeader(ZipUpdate update) + { + var entry = update.OutEntry; + + // TODO: Local offset will require adjusting for multi-disk zip files. + entry.Offset = baseStream_.Position; + + // TODO: Need to clear any entry flags that dont make sense or throw an exception here. + if (update.Command != UpdateCommand.Copy) + { + if (entry.CompressionMethod == CompressionMethod.Deflated) + { + if (entry.Size == 0) + { + // No need to compress - no data. + entry.CompressedSize = entry.Size; + entry.Crc = 0; + entry.CompressionMethod = CompressionMethod.Stored; + } + } + else if (entry.CompressionMethod == CompressionMethod.Stored) + { + entry.Flags &= ~(int)GeneralBitFlags.Descriptor; + } + + if (HaveKeys) + { + entry.IsCrypted = true; + if (entry.Crc < 0) + { + entry.Flags |= (int)GeneralBitFlags.Descriptor; + } + } + else + { + entry.IsCrypted = false; + } + + switch (useZip64_) + { + case UseZip64.Dynamic: + if (entry.Size < 0) + { + entry.ForceZip64(); + } + break; + + case UseZip64.On: + entry.ForceZip64(); + break; + + case UseZip64.Off: + // Do nothing. The entry itself may be using Zip64 independently. + break; + } + } + + // Write the local file header + WriteLEInt(ZipConstants.LocalHeaderSignature); + + WriteLEShort(entry.Version); + WriteLEShort(entry.Flags); + + WriteLEShort((byte)entry.CompressionMethodForHeader); + WriteLEInt((int)entry.DosTime); + + if (!entry.HasCrc) + { + // Note patch address for updating CRC later. + update.CrcPatchOffset = baseStream_.Position; + WriteLEInt(0); + } + else + { + WriteLEInt(unchecked((int)entry.Crc)); + } + + if (entry.LocalHeaderRequiresZip64) + { + WriteLEInt(-1); + WriteLEInt(-1); + } + else + { + if ((entry.CompressedSize < 0) || (entry.Size < 0)) + { + update.SizePatchOffset = baseStream_.Position; + } + + WriteLEInt((int)entry.CompressedSize); + WriteLEInt((int)entry.Size); + } + + var name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.LocalHeaderRequiresZip64) + { + ed.StartNewEntry(); + + // Local entry header always includes size and compressed size. + // NOTE the order of these fields is reversed when compared to the normal headers! + ed.AddLeLong(entry.Size); + ed.AddLeLong(entry.CompressedSize); + ed.AddNewEntry(1); + } + else + { + ed.Delete(1); + } + + entry.ExtraData = ed.GetEntryData(); + + WriteLEShort(name.Length); + WriteLEShort(entry.ExtraData.Length); + + if (name.Length > 0) + { + baseStream_.Write(name, 0, name.Length); + } + + if (entry.LocalHeaderRequiresZip64) + { + if (!ed.Find(1)) + { + throw new ZipException("Internal error cannot find extra data"); + } + + update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; + } + + if (entry.ExtraData.Length > 0) + { + baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); + } + } + + private int WriteCentralDirectoryHeader(ZipEntry entry) + { + if (entry.CompressedSize < 0) + { + throw new ZipException("Attempt to write central directory entry with unknown csize"); + } + + if (entry.Size < 0) + { + throw new ZipException("Attempt to write central directory entry with unknown size"); + } + + if (entry.Crc < 0) + { + throw new ZipException("Attempt to write central directory entry with unknown crc"); + } + + // Write the central file header + WriteLEInt(ZipConstants.CentralHeaderSignature); + + // Version made by + WriteLEShort((entry.HostSystem << 8) | entry.VersionMadeBy); + + // Version required to extract + WriteLEShort(entry.Version); + + WriteLEShort(entry.Flags); + + unchecked + { + WriteLEShort((byte)entry.CompressionMethodForHeader); + WriteLEInt((int)entry.DosTime); + WriteLEInt((int)entry.Crc); + } + + var useExtraCompressedSize = false; //Do we want to store the compressed size in the extra data? + if (entry.IsZip64Forced() || (entry.CompressedSize >= 0xffffffff)) + { + useExtraCompressedSize = true; + WriteLEInt(-1); + } + else + { + WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); + } + + var useExtraUncompressedSize = false; //Do we want to store the uncompressed size in the extra data? + if (entry.IsZip64Forced() || (entry.Size >= 0xffffffff)) + { + useExtraUncompressedSize = true; + WriteLEInt(-1); + } + else + { + WriteLEInt((int)entry.Size); + } + + var name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name is too long."); + } + + WriteLEShort(name.Length); + + // Central header extra data is different to local header version so regenerate. + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.CentralHeaderRequiresZip64) + { + ed.StartNewEntry(); + + if (useExtraUncompressedSize) + { + ed.AddLeLong(entry.Size); + } + + if (useExtraCompressedSize) + { + ed.AddLeLong(entry.CompressedSize); + } + + if (entry.Offset >= 0xffffffff) + { + ed.AddLeLong(entry.Offset); + } + + // Number of disk on which this file starts isnt supported and is never written here. + ed.AddNewEntry(1); + } + else + { + // Should have already be done when local header was added. + ed.Delete(1); + } + + var centralExtraData = ed.GetEntryData(); + + WriteLEShort(centralExtraData.Length); + WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); + + WriteLEShort(0); // disk number + WriteLEShort(0); // internal file attributes + + // External file attributes... + if (entry.ExternalFileAttributes != -1) + { + WriteLEInt(entry.ExternalFileAttributes); + } + else + { + if (entry.IsDirectory) + { + WriteLEUint(16); + } + else + { + WriteLEUint(0); + } + } + + if (entry.Offset >= 0xffffffff) + { + WriteLEUint(0xffffffff); + } + else + { + WriteLEUint((uint)(int)entry.Offset); + } + + if (name.Length > 0) + { + baseStream_.Write(name, 0, name.Length); + } + + if (centralExtraData.Length > 0) + { + baseStream_.Write(centralExtraData, 0, centralExtraData.Length); + } + + var rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : Empty.Array(); + + if (rawComment.Length > 0) + { + baseStream_.Write(rawComment, 0, rawComment.Length); + } + + return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; + } + + #endregion Writing Values/Headers + + private void PostUpdateCleanup() + { + updateDataSource_ = null; + updates_ = null; + updateIndex_ = null; + + if (archiveStorage_ != null) + { + archiveStorage_.Dispose(); + archiveStorage_ = null; + } + } + + private string GetTransformedFileName(string name) + { + var transform = NameTransform; + return (transform != null) ? + transform.TransformFile(name) : + name; + } + + private string GetTransformedDirectoryName(string name) + { + var transform = NameTransform; + return (transform != null) ? + transform.TransformDirectory(name) : + name; + } + + /// + /// Get a raw memory buffer. + /// + /// Returns a raw memory buffer. + private byte[] GetBuffer() + { + copyBuffer_ ??= new byte[bufferSize_]; + return copyBuffer_; + } + + private void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) + { + // Don't include the signature size to allow copy without seeking + var bytesToCopy = GetDescriptorSize(update, false); + + // Don't touch the source stream if no descriptor is present + if (bytesToCopy == 0) + return; + + var buffer = GetBuffer(); + + // Copy the first 4 bytes of the descriptor + source.Read(buffer, 0, sizeof(int)); + dest.Write(buffer, 0, sizeof(int)); + + if (BitConverter.ToUInt32(buffer, 0) != ZipConstants.DataDescriptorSignature) + { + // The initial bytes wasn't the descriptor, reduce the pending byte count + bytesToCopy -= buffer.Length; + } + + while (bytesToCopy > 0) + { + var readSize = Math.Min(buffer.Length, bytesToCopy); + + var bytesRead = source.Read(buffer, 0, readSize); + if (bytesRead > 0) + { + dest.Write(buffer, 0, bytesRead); + bytesToCopy -= bytesRead; + } + else + { + throw new ZipException("Unxpected end of stream"); + } + } + } + + private void CopyBytes(ZipUpdate update, Stream destination, Stream source, + long bytesToCopy, bool updateCrc) + { + if (destination == source) + { + throw new InvalidOperationException("Destination and source are the same"); + } + + // NOTE: Compressed size is updated elsewhere. + var crc = new Crc32(); + var buffer = GetBuffer(); + + var targetBytes = bytesToCopy; + long totalBytesRead = 0; + + int bytesRead; + do + { + var readSize = buffer.Length; + + if (bytesToCopy < readSize) + { + readSize = (int)bytesToCopy; + } + + bytesRead = source.Read(buffer, 0, readSize); + if (bytesRead > 0) + { + if (updateCrc) + { + crc.Update(new ArraySegment(buffer, 0, bytesRead)); + } + destination.Write(buffer, 0, bytesRead); + bytesToCopy -= bytesRead; + totalBytesRead += bytesRead; + } + } + while ((bytesRead > 0) && (bytesToCopy > 0)); + + if (totalBytesRead != targetBytes) + { + throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); + } + + if (updateCrc) + { + update.OutEntry.Crc = crc.Value; + } + } + + /// + /// Get the size of the source descriptor for a . + /// + /// The update to get the size for. + /// Whether to include the signature size + /// The descriptor size, zero if there isn't one. + private static int GetDescriptorSize(ZipUpdate update, bool includingSignature) + { + if (!((GeneralBitFlags)update.Entry.Flags).HasFlag(GeneralBitFlags.Descriptor)) + return 0; + + var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64 + ? ZipConstants.Zip64DataDescriptorSize + : ZipConstants.DataDescriptorSize; + + return includingSignature + ? descriptorWithSignature + : descriptorWithSignature - sizeof(int); + } + + private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) + { + var buffer = GetBuffer(); + ; + + stream.Position = sourcePosition; + stream.Read(buffer, 0, sizeof(int)); + var sourceHasSignature = BitConverter.ToUInt32(buffer, 0) == ZipConstants.DataDescriptorSignature; + + var bytesToCopy = GetDescriptorSize(update, sourceHasSignature); + + while (bytesToCopy > 0) + { + stream.Position = sourcePosition; + + var bytesRead = stream.Read(buffer, 0, bytesToCopy); + if (bytesRead > 0) + { + stream.Position = destinationPosition; + stream.Write(buffer, 0, bytesRead); + bytesToCopy -= bytesRead; + destinationPosition += bytesRead; + sourcePosition += bytesRead; + } + else + { + throw new ZipException("Unexpected end of stream"); + } + } + } + + private void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition) + { + var bytesToCopy = update.Entry.CompressedSize; + + // NOTE: Compressed size is updated elsewhere. + var crc = new Crc32(); + var buffer = GetBuffer(); + + var targetBytes = bytesToCopy; + long totalBytesRead = 0; + + int bytesRead; + do + { + var readSize = buffer.Length; + + if (bytesToCopy < readSize) + { + readSize = (int)bytesToCopy; + } + + stream.Position = sourcePosition; + bytesRead = stream.Read(buffer, 0, readSize); + if (bytesRead > 0) + { + if (updateCrc) + { + crc.Update(new ArraySegment(buffer, 0, bytesRead)); + } + stream.Position = destinationPosition; + stream.Write(buffer, 0, bytesRead); + + destinationPosition += bytesRead; + sourcePosition += bytesRead; + bytesToCopy -= bytesRead; + totalBytesRead += bytesRead; + } + } + while ((bytesRead > 0) && (bytesToCopy > 0)); + + if (totalBytesRead != targetBytes) + { + throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); + } + + if (updateCrc) + { + update.OutEntry.Crc = crc.Value; + } + } + + private int FindExistingUpdate(ZipEntry entry) + { + var result = -1; + if (updateIndex_.ContainsKey(entry.Name)) + { + result = updateIndex_[entry.Name]; + } + /* + // This is slow like the coming of the next ice age but takes less storage and may be useful + // for CF? + for (int index = 0; index < updates_.Count; ++index) + { + ZipUpdate zu = ( ZipUpdate )updates_[index]; + if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && + (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { + result = index; + break; + } + } + */ + return result; + } + + private int FindExistingUpdate(string fileName, bool isEntryName = false) + { + var result = -1; + + var convertedName = !isEntryName ? GetTransformedFileName(fileName) : fileName; + + if (updateIndex_.ContainsKey(convertedName)) + { + result = updateIndex_[convertedName]; + } + + /* + // This is slow like the coming of the next ice age but takes less storage and may be useful + // for CF? + for ( int index = 0; index < updates_.Count; ++index ) { + if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, + true, CultureInfo.InvariantCulture) == 0 ) { + result = index; + break; + } + } + */ + + return result; + } + + /// + /// Get an output stream for the specified + /// + /// The entry to get an output stream for. + /// The output stream obtained for the entry. + private Stream GetOutputStream(ZipEntry entry) + { + var result = baseStream_; + + if (entry.IsCrypted == true) + { + result = CreateAndInitEncryptionStream(result, entry); + } + + switch (entry.CompressionMethod) + { + case CompressionMethod.Stored: + if (!entry.IsCrypted) + { + // If there is an encryption stream in use, that can be returned directly + // otherwise, wrap the base stream in an UncompressedStream instead of returning it directly + result = new UncompressedStream(result); + } + break; + + case CompressionMethod.Deflated: + var dos = new DeflaterOutputStream(result, new Deflater(9, true)) + { + // If there is an encryption stream in use, then we want that to be disposed when the deflator stream is disposed + // If not, then we don't want it to dispose the base stream + IsStreamOwner = entry.IsCrypted + }; + result = dos; + break; + + case CompressionMethod.BZip2: + var bzos = new BZip2.BZip2OutputStream(result) + { + // If there is an encryption stream in use, then we want that to be disposed when the BZip2OutputStream stream is disposed + // If not, then we don't want it to dispose the base stream + IsStreamOwner = entry.IsCrypted + }; + result = bzos; + break; + + default: + throw new ZipException("Unknown compression method " + entry.CompressionMethod); + } + return result; + } + + private void AddEntry(ZipFile workFile, ZipUpdate update) + { + Stream source = null; + + if (update.Entry.IsFile) + { + source = update.GetSource(); + + source ??= updateDataSource_.GetSource(update.Entry, update.Filename); + } + + var useCrc = update.Entry.AESKeySize == 0; + + if (source != null) + { + using (source) + { + var sourceStreamLength = source.Length; + if (update.OutEntry.Size < 0) + { + update.OutEntry.Size = sourceStreamLength; + } + else + { + // Check for errant entries. + if (update.OutEntry.Size != sourceStreamLength) + { + throw new ZipException("Entry size/stream size mismatch"); + } + } + + workFile.WriteLocalEntryHeader(update); + + var dataStart = workFile.baseStream_.Position; + + using (var output = workFile.GetOutputStream(update.OutEntry)) + { + CopyBytes(update, output, source, sourceStreamLength, useCrc); + } + + var dataEnd = workFile.baseStream_.Position; + update.OutEntry.CompressedSize = dataEnd - dataStart; + + if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) + { + var helper = new ZipHelperStream(workFile.baseStream_); + helper.WriteDataDescriptor(update.OutEntry); + } + } + } + else + { + workFile.WriteLocalEntryHeader(update); + update.OutEntry.CompressedSize = 0; + } + } + + private void ModifyEntry(ZipFile workFile, ZipUpdate update) + { + workFile.WriteLocalEntryHeader(update); + var dataStart = workFile.baseStream_.Position; + + // TODO: This is slow if the changes don't effect the data!! + if (update.Entry.IsFile && (update.Filename != null)) + { + using var output = workFile.GetOutputStream(update.OutEntry); + using var source = this.GetInputStream(update.Entry); + CopyBytes(update, output, source, source.Length, true); + } + + var dataEnd = workFile.baseStream_.Position; + update.Entry.CompressedSize = dataEnd - dataStart; + } + + private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) + { + var skipOver = false || update.Entry.Offset == destinationPosition; + + if (!skipOver) + { + baseStream_.Position = destinationPosition; + workFile.WriteLocalEntryHeader(update); + destinationPosition = baseStream_.Position; + } + + const int NameLengthOffset = 26; + + // TODO: Add base for SFX friendly handling + var entryDataOffset = update.Entry.Offset + NameLengthOffset; + + baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); + + // Clumsy way of handling retrieving the original name and extra data length for now. + // TODO: Stop re-reading name and data length in CopyEntryDirect. + + uint nameLength = ReadLEUshort(); + uint extraLength = ReadLEUshort(); + + var sourcePosition = baseStream_.Position + nameLength + extraLength; + + if (skipOver) + { + if (update.OffsetBasedSize != -1) + { + destinationPosition += update.OffsetBasedSize; + } + else + { + // Skip entry header + destinationPosition += sourcePosition - entryDataOffset + NameLengthOffset; + + // Skip entry compressed data + destinationPosition += update.Entry.CompressedSize; + + // Seek to end of entry to check for descriptor signature + baseStream_.Seek(destinationPosition, SeekOrigin.Begin); + + var descriptorHasSignature = ReadLEUint() == ZipConstants.DataDescriptorSignature; + + // Skip descriptor and it's signature (if present) + destinationPosition += GetDescriptorSize(update, descriptorHasSignature); + } + } + else + { + if (update.Entry.CompressedSize > 0) + { + CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); + } + CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); + } + } + + private void CopyEntry(ZipFile workFile, ZipUpdate update) + { + workFile.WriteLocalEntryHeader(update); + + if (update.Entry.CompressedSize > 0) + { + const int NameLengthOffset = 26; + + var entryDataOffset = update.Entry.Offset + NameLengthOffset; + + // TODO: This wont work for SFX files! + baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); + + uint nameLength = ReadLEUshort(); + uint extraLength = ReadLEUshort(); + + baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); + + CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); + } + CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); + } + + private void Reopen(Stream source) + { + isNewArchive_ = false; + baseStream_ = source ?? throw new ZipException("Failed to reopen archive - no source"); + ReadEntries(); + } + + private void Reopen() + { + if (Name == null) + { + throw new InvalidOperationException("Name is not known cannot Reopen"); + } + + Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); + } + + private void UpdateCommentOnly() + { + var baseLength = baseStream_.Length; + + ZipHelperStream updateFile; + if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) + { + var copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); + updateFile = new ZipHelperStream(copyStream) + { + IsStreamOwner = true + }; + + baseStream_.Dispose(); + baseStream_ = null; + } + else + { + if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) + { + // TODO: archiveStorage wasnt originally intended for this use. + // Need to revisit this to tidy up handling as archive storage currently doesnt + // handle the original stream well. + // The problem is when using an existing zip archive with an in memory archive storage. + // The open stream wont support writing but the memory storage should open the same file not an in memory one. + + // Need to tidy up the archive storage interface and contract basically. + baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); + updateFile = new ZipHelperStream(baseStream_); + } + else + { + baseStream_.Dispose(); + baseStream_ = null; + updateFile = new ZipHelperStream(Name); + } + } + + using (updateFile) + { + var locatedCentralDirOffset = + updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, + baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); + if (locatedCentralDirOffset < 0) + { + throw new ZipException("Cannot find central directory"); + } + + const int CentralHeaderCommentSizeOffset = 16; + updateFile.Position += CentralHeaderCommentSizeOffset; + + var rawComment = newComment_.RawComment; + + updateFile.WriteLEShort(rawComment.Length); + updateFile.Write(rawComment, 0, rawComment.Length); + updateFile.SetLength(updateFile.Position); + } + + if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) + { + Reopen(archiveStorage_.ConvertTemporaryToFinal()); + } + else + { + ReadEntries(); + } + } + + /// + /// Class used to sort updates. + /// + private class UpdateComparer : IComparer + { + /// + /// Compares two objects and returns a value indicating whether one is + /// less than, equal to or greater than the other. + /// + /// First object to compare + /// Second object to compare. + /// Compare result. + public int Compare(ZipUpdate x, ZipUpdate y) + { + int result; + + if (x == null) + { + result = y == null ? 0 : -1; + } + else if (y == null) + { + result = 1; + } + else + { + var xCmdValue = (x.Command is UpdateCommand.Copy or UpdateCommand.Modify) ? 0 : 1; + var yCmdValue = (y.Command is UpdateCommand.Copy or UpdateCommand.Modify) ? 0 : 1; + + result = xCmdValue - yCmdValue; + if (result == 0) + { + var offsetDiff = x.Entry.Offset - y.Entry.Offset; + if (offsetDiff < 0) + { + result = -1; + } + else + { + result = offsetDiff == 0 ? 0 : 1; + } + } + } + return result; + } + } + + private void RunUpdates() + { + long sizeEntries = 0; + var directUpdate = false; + long destinationPosition = 0; // NOT SFX friendly + + ZipFile workFile; + + if (IsNewArchive) + { + workFile = this; + workFile.baseStream_.Position = 0; + directUpdate = true; + } + else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) + { + workFile = this; + workFile.baseStream_.Position = 0; + directUpdate = true; + + // Sort the updates by offset within copies/modifies, then adds. + // This ensures that data required by copies will not be overwritten. + updates_.Sort(new UpdateComparer()); + } + else + { + workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); + workFile.UseZip64 = UseZip64; + + if (key != null) + { + workFile.key = (byte[])key.Clone(); + } + } + + long endOfStream; + try + { + foreach (var update in updates_) + { + if (update != null) + { + switch (update.Command) + { + case UpdateCommand.Copy: + if (directUpdate) + { + CopyEntryDirect(workFile, update, ref destinationPosition); + } + else + { + CopyEntry(workFile, update); + } + break; + + case UpdateCommand.Modify: + // TODO: Direct modifying of an entry will take some legwork. + ModifyEntry(workFile, update); + break; + + case UpdateCommand.Add: + if (!IsNewArchive && directUpdate) + { + workFile.baseStream_.Position = destinationPosition; + } + + AddEntry(workFile, update); + + if (directUpdate) + { + destinationPosition = workFile.baseStream_.Position; + } + break; + } + } + } + + if (!IsNewArchive && directUpdate) + { + workFile.baseStream_.Position = destinationPosition; + } + + var centralDirOffset = workFile.baseStream_.Position; + + foreach (var update in updates_) + { + if (update != null) + { + sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); + } + } + + var theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_); + using (var zhs = new ZipHelperStream(workFile.baseStream_)) + { + zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); + } + + endOfStream = workFile.baseStream_.Position; + + // And now patch entries... + foreach (var update in updates_) + { + if (update != null) + { + // If the size of the entry is zero leave the crc as 0 as well. + // The calculated crc will be all bits on... + if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) + { + workFile.baseStream_.Position = update.CrcPatchOffset; + workFile.WriteLEInt((int)update.OutEntry.Crc); + } + + if (update.SizePatchOffset > 0) + { + workFile.baseStream_.Position = update.SizePatchOffset; + if (update.OutEntry.LocalHeaderRequiresZip64) + { + workFile.WriteLeLong(update.OutEntry.Size); + workFile.WriteLeLong(update.OutEntry.CompressedSize); + } + else + { + workFile.WriteLEInt((int)update.OutEntry.CompressedSize); + workFile.WriteLEInt((int)update.OutEntry.Size); + } + } + } + } + } + catch + { + workFile.Close(); + if (!directUpdate && (workFile.Name != null)) + { + File.Delete(workFile.Name); + } + throw; + } + + if (directUpdate) + { + workFile.baseStream_.SetLength(endOfStream); + workFile.baseStream_.Flush(); + isNewArchive_ = false; + ReadEntries(); + } + else + { + baseStream_.Dispose(); + Reopen(archiveStorage_.ConvertTemporaryToFinal()); + } + } + + private void CheckUpdating() + { + if (updates_ == null) + { + throw new InvalidOperationException("BeginUpdate has not been called"); + } + } + + #endregion Update Support + + #region ZipUpdate class + + /// + /// Represents a pending update to a Zip file. + /// + private class ZipUpdate + { + #region Constructors + + public ZipUpdate(string fileName, ZipEntry entry) + { + command_ = UpdateCommand.Add; + entry_ = entry; + filename_ = fileName; + } + + [Obsolete] + public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) + { + command_ = UpdateCommand.Add; + entry_ = new ZipEntry(entryName) + { + CompressionMethod = compressionMethod + }; + filename_ = fileName; + } + + [Obsolete] + public ZipUpdate(string fileName, string entryName) + : this(fileName, entryName, CompressionMethod.Deflated) + { + // Do nothing. + } + + [Obsolete] + public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) + { + command_ = UpdateCommand.Add; + entry_ = new ZipEntry(entryName) + { + CompressionMethod = compressionMethod + }; + dataSource_ = dataSource; + } + + public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) + { + command_ = UpdateCommand.Add; + entry_ = entry; + dataSource_ = dataSource; + } + + public ZipUpdate(ZipEntry original, ZipEntry updated) + { + throw new ZipException("Modify not currently supported"); + /* + command_ = UpdateCommand.Modify; + entry_ = ( ZipEntry )original.Clone(); + outEntry_ = ( ZipEntry )updated.Clone(); + */ + } + + public ZipUpdate(UpdateCommand command, ZipEntry entry) + { + command_ = command; + entry_ = (ZipEntry)entry.Clone(); + } + + /// + /// Copy an existing entry. + /// + /// The existing entry to copy. + public ZipUpdate(ZipEntry entry) + : this(UpdateCommand.Copy, entry) + { + // Do nothing. + } + + #endregion Constructors + + /// + /// Get the for this update. + /// + /// This is the source or original entry. + public ZipEntry Entry + { + get { return entry_; } + } + + /// + /// Get the that will be written to the updated/new file. + /// + public ZipEntry OutEntry + { + get + { + outEntry_ ??= (ZipEntry)entry_.Clone(); + + return outEntry_; + } + } + + /// + /// Get the command for this update. + /// + public UpdateCommand Command + { + get { return command_; } + } + + /// + /// Get the filename if any for this update. Null if none exists. + /// + public string Filename + { + get { return filename_; } + } + + /// + /// Get/set the location of the size patch for this update. + /// + public long SizePatchOffset + { + get { return sizePatchOffset_; } + set { sizePatchOffset_ = value; } + } + + /// + /// Get /set the location of the crc patch for this update. + /// + public long CrcPatchOffset + { + get { return crcPatchOffset_; } + set { crcPatchOffset_ = value; } + } + + /// + /// Get/set the size calculated by offset. + /// Specifically, the difference between this and next entry's starting offset. + /// + public long OffsetBasedSize + { + get { return _offsetBasedSize; } + set { _offsetBasedSize = value; } + } + + public Stream GetSource() + { + Stream result = null; + if (dataSource_ != null) + { + result = dataSource_.GetSource(); + } + + return result; + } + + #region Instance Fields + + private readonly ZipEntry entry_; + private ZipEntry outEntry_; + private readonly UpdateCommand command_; + private readonly IStaticDataSource dataSource_; + private readonly string filename_; + private long sizePatchOffset_ = -1; + private long crcPatchOffset_ = -1; + private long _offsetBasedSize = -1; + + #endregion Instance Fields + } + + #endregion ZipUpdate class + + #endregion Updating + + #region Disposing + + #region IDisposable Members + + void IDisposable.Dispose() + { + Close(); + } + + #endregion IDisposable Members + + private void DisposeInternal(bool disposing) + { + if (!isDisposed_) + { + isDisposed_ = true; + entries_ = Empty.Array(); + + if (IsStreamOwner && (baseStream_ != null)) + { + lock (baseStream_) + { + baseStream_.Dispose(); + } + } + + PostUpdateCleanup(); + } + } + + /// + /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; + /// false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + DisposeInternal(disposing); + } + + #endregion Disposing + + #region Internal routines + + #region Reading + + /// + /// Read an unsigned short in little endian byte order. + /// + /// Returns the value read. + /// + /// The stream ends prematurely + /// + private ushort ReadLEUshort() + { + var data1 = baseStream_.ReadByte(); + + if (data1 < 0) + { + throw new EndOfStreamException("End of stream"); + } + + var data2 = baseStream_.ReadByte(); + + return data2 < 0 ? throw new EndOfStreamException("End of stream") : unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); + } + + /// + /// Read a uint in little endian byte order. + /// + /// Returns the value read. + /// + /// An i/o error occurs. + /// + /// + /// The file ends prematurely + /// + private uint ReadLEUint() + { + return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); + } + + private ulong ReadLEUlong() + { + return ReadLEUint() | ((ulong)ReadLEUint() << 32); + } + + #endregion Reading + + // NOTE this returns the offset of the first byte after the signature. + private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) + { + using var les = new ZipHelperStream(baseStream_); + return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); + } + + /// + /// Search for and read the central directory of a zip file filling the entries array. + /// + /// + /// An i/o error occurs. + /// + /// + /// The central directory is malformed or cannot be found + /// + private void ReadEntries() + { + // Search for the End Of Central Directory. When a zip comment is + // present the directory will start earlier + // + // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. + // This should be compatible with both SFX and ZIP files but has only been tested for Zip files + // If a SFX file has the Zip data attached as a resource and there are other resources occurring later then + // this could be invalid. + // Could also speed this up by reading memory in larger blocks. + + if (baseStream_.CanSeek == false) + { + throw new ZipException("ZipFile stream must be seekable"); + } + + var locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, + baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); + + if (locatedEndOfCentralDir < 0) + { + throw new ZipException("Cannot find central directory"); + } + + // Read end of central directory record + var thisDiskNumber = ReadLEUshort(); + var startCentralDirDisk = ReadLEUshort(); + ulong entriesForThisDisk = ReadLEUshort(); + ulong entriesForWholeCentralDir = ReadLEUshort(); + ulong centralDirSize = ReadLEUint(); + long offsetOfCentralDir = ReadLEUint(); + uint commentSize = ReadLEUshort(); + + if (commentSize > 0) + { + var comment = new byte[commentSize]; + + StreamUtils.ReadFully(baseStream_, comment); + comment_ = ZipStrings.ConvertToString(comment); + } + else + { + comment_ = string.Empty; + } + + var isZip64 = false; + var requireZip64 = false; + + // Check if zip64 header information is required. + if ((thisDiskNumber == 0xffff) || + (startCentralDirDisk == 0xffff) || + (entriesForThisDisk == 0xffff) || + (entriesForWholeCentralDir == 0xffff) || + (centralDirSize == 0xffffffff) || + (offsetOfCentralDir == 0xffffffff)) + { + requireZip64 = true; + } + + // #357 - always check for the existance of the Zip64 central directory. + // #403 - Take account of the fixed size of the locator when searching. + // Subtract from locatedEndOfCentralDir so that the endLocation is the location of EndOfCentralDirectorySignature, + // rather than the data following the signature. + var locatedZip64EndOfCentralDirLocator = LocateBlockWithSignature( + ZipConstants.Zip64CentralDirLocatorSignature, + locatedEndOfCentralDir - 4, + ZipConstants.Zip64EndOfCentralDirectoryLocatorSize, + 0); + + if (locatedZip64EndOfCentralDirLocator < 0) + { + if (requireZip64) + { + // This is only an error in cases where the Zip64 directory is required. + throw new ZipException("Cannot find Zip64 locator"); + } + } + else + { + isZip64 = true; + + // number of the disk with the start of the zip64 end of central directory 4 bytes + // relative offset of the zip64 end of central directory record 8 bytes + // total number of disks 4 bytes + ReadLEUint(); // startDisk64 is not currently used + var offset64 = ReadLEUlong(); + var totalDisks = ReadLEUint(); + + baseStream_.Position = (long)offset64; + long sig64 = ReadLEUint(); + + if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) + { + throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); + } + + // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. + var recordSize = ReadLEUlong(); + int versionMadeBy = ReadLEUshort(); + int versionToExtract = ReadLEUshort(); + var thisDisk = ReadLEUint(); + var centralDirDisk = ReadLEUint(); + entriesForThisDisk = ReadLEUlong(); + entriesForWholeCentralDir = ReadLEUlong(); + centralDirSize = ReadLEUlong(); + offsetOfCentralDir = (long)ReadLEUlong(); + + // NOTE: zip64 extensible data sector (variable size) is ignored. + } + + entries_ = new ZipEntry[entriesForThisDisk]; + + // SFX/embedded support, find the offset of the first entry vis the start of the stream + // This applies to Zip files that are appended to the end of an SFX stub. + // Or are appended as a resource to an executable. + // Zip files created by some archivers have the offsets altered to reflect the true offsets + // and so dont require any adjustment here... + // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? + if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) + { + offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); + if (offsetOfFirstEntry <= 0) + { + throw new ZipException("Invalid embedded zip archive"); + } + } + + baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); + + for (ulong i = 0; i < entriesForThisDisk; i++) + { + if (ReadLEUint() != ZipConstants.CentralHeaderSignature) + { + throw new ZipException("Wrong Central Directory signature"); + } + + int versionMadeBy = ReadLEUshort(); + int versionToExtract = ReadLEUshort(); + int bitFlags = ReadLEUshort(); + int method = ReadLEUshort(); + var dostime = ReadLEUint(); + var crc = ReadLEUint(); + var csize = (long)ReadLEUint(); + var size = (long)ReadLEUint(); + int nameLen = ReadLEUshort(); + int extraLen = ReadLEUshort(); + int commentLen = ReadLEUshort(); + + int diskStartNo = ReadLEUshort(); // Not currently used + int internalAttributes = ReadLEUshort(); // Not currently used + + var externalAttributes = ReadLEUint(); + long offset = ReadLEUint(); + + var buffer = new byte[Math.Max(nameLen, commentLen)]; + + StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); + var name = ZipStrings.ConvertToStringExt(bitFlags, buffer, nameLen); + + var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method) + { + Crc = crc & 0xffffffffL, + Size = size & 0xffffffffL, + CompressedSize = csize & 0xffffffffL, + Flags = bitFlags, + DosTime = dostime, + ZipFileIndex = (long)i, + Offset = offset, + ExternalFileAttributes = (int)externalAttributes + }; + + entry.CryptoCheckValue = (bitFlags & 8) == 0 ? (byte)(crc >> 24) : (byte)((dostime >> 8) & 0xff); + + if (extraLen > 0) + { + var extra = new byte[extraLen]; + StreamUtils.ReadFully(baseStream_, extra); + entry.ExtraData = extra; + } + + entry.ProcessExtraData(false); + + if (commentLen > 0) + { + StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); + entry.Comment = ZipStrings.ConvertToStringExt(bitFlags, buffer, commentLen); + } + + entries_[i] = entry; + } + } + + /// + /// Locate the data for a given entry. + /// + /// + /// The start offset of the data. + /// + /// + /// The stream ends prematurely + /// + /// + /// The local header signature is invalid, the entry and central header file name lengths are different + /// or the local and entry compression methods dont match + /// + private long LocateEntry(ZipEntry entry) + { + return TestLocalHeader(entry, HeaderTest.Extract); + } + + private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) + { + CryptoStream result; + if (entry.CompressionMethodForHeader == CompressionMethod.WinZipAES) + { + if (entry.Version >= ZipConstants.VERSION_AES) + { + // Issue #471 - accept an empty string as a password, but reject null. + OnKeysRequired(entry.Name); + if (rawPassword_ == null) + { + throw new ZipException("No password available for AES encrypted stream"); + } + var saltLen = entry.AESSaltLen; + var saltBytes = new byte[saltLen]; + var saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, 0, saltLen); + if (saltIn != saltLen) + throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); + // + var pwdVerifyRead = new byte[2]; + StreamUtils.ReadFully(baseStream, pwdVerifyRead); + var blockSize = entry.AESKeySize / 8; // bits to bytes + + var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); + var pwdVerifyCalc = decryptor.PwdVerifier; + if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) + throw new ZipException("Invalid password for AES"); + result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); + } + else + { + throw new ZipException("Decryption method not supported"); + } + } + else + { + if ((entry.Version < ZipConstants.VersionStrongEncryption) + || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) + { + var classicManaged = new PkzipClassicManaged(); + + OnKeysRequired(entry.Name); + if (HaveKeys == false) + { + throw new ZipException("No password available for encrypted stream"); + } + + result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); + CheckClassicPassword(result, entry); + } + else + { + // We don't support PKWare strong encryption + throw new ZipException("Decryption method not supported"); + } + } + + return result; + } + + private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) + { + CryptoStream result = null; + if ((entry.Version < ZipConstants.VersionStrongEncryption) + || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) + { + var classicManaged = new PkzipClassicManaged(); + + OnKeysRequired(entry.Name); + if (HaveKeys == false) + { + throw new ZipException("No password available for encrypted stream"); + } + + // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream + // which doesnt do this. + result = new CryptoStream(new UncompressedStream(baseStream), + classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); + + if ((entry.Crc < 0) || (entry.Flags & 8) != 0) + { + WriteEncryptionHeader(result, entry.DosTime << 16); + } + else + { + WriteEncryptionHeader(result, entry.Crc); + } + } + return result; + } + + private static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) + { + var cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; + StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); + if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) + throw new ZipException("Invalid password"); + } + + private static void WriteEncryptionHeader(Stream stream, long crcValue) + { + var cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; + var rng = RandomNumberGenerator.Create(); + rng.GetBytes(cryptBuffer); + cryptBuffer[11] = (byte)(crcValue >> 24); + stream.Write(cryptBuffer, 0, cryptBuffer.Length); + } + + #endregion Internal routines + + #region Instance Fields + + private bool isDisposed_; + private string name_; + private string comment_; + private string rawPassword_; + private Stream baseStream_; + private bool isStreamOwner; + private long offsetOfFirstEntry; + private ZipEntry[] entries_; + private byte[] key; + private bool isNewArchive_; + + // Default is dynamic which is not backwards compatible and can cause problems + // with XP's built in compression which cant read Zip64 archives. + // However it does avoid the situation were a large file is added and cannot be completed correctly. + // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. + private UseZip64 useZip64_ = UseZip64.Dynamic; + + #region Zip Update Instance Fields + + private List updates_; + private long updateCount_; // Count is managed manually as updates_ can contain nulls! + private Dictionary updateIndex_; + private IArchiveStorage archiveStorage_; + private IDynamicDataSource updateDataSource_; + private bool contentsEdited_; + private int bufferSize_ = DefaultBufferSize; + private byte[] copyBuffer_; + private ZipString newComment_; + private bool commentEdited_; + private IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); + + #endregion Zip Update Instance Fields + + #endregion Instance Fields + + #region Support Classes + + /// + /// Represents a string from a which is stored as an array of bytes. + /// + private class ZipString + { + #region Constructors + + /// + /// Initialise a with a string. + /// + /// The textual string form. + public ZipString(string comment) + { + comment_ = comment; + isSourceString_ = true; + } + + /// + /// Initialise a using a string in its binary 'raw' form. + /// + /// + public ZipString(byte[] rawString) + { + rawComment_ = rawString; + } + + #endregion Constructors + + /// + /// Get a value indicating the original source of data for this instance. + /// True if the source was a string; false if the source was binary data. + /// + public bool IsSourceString + { + get { return isSourceString_; } + } + + /// + /// Get the length of the comment when represented as raw bytes. + /// + public int RawLength + { + get + { + MakeBytesAvailable(); + return rawComment_.Length; + } + } + + /// + /// Get the comment in its 'raw' form as plain bytes. + /// + public byte[] RawComment + { + get + { + MakeBytesAvailable(); + return (byte[])rawComment_.Clone(); + } + } + + /// + /// Reset the comment to its initial state. + /// + public void Reset() + { + if (isSourceString_) + { + rawComment_ = null; + } + else + { + comment_ = null; + } + } + + private void MakeTextAvailable() + { + comment_ ??= ZipStrings.ConvertToString(rawComment_); + } + + private void MakeBytesAvailable() + { + rawComment_ ??= ZipStrings.ConvertToArray(comment_); + } + + /// + /// Implicit conversion of comment to a string. + /// + /// The to convert to a string. + /// The textual equivalent for the input value. + public static implicit operator string(ZipString zipString) + { + zipString.MakeTextAvailable(); + return zipString.comment_; + } + + #region Instance Fields + + private string comment_; + private byte[] rawComment_; + private readonly bool isSourceString_; + + #endregion Instance Fields + } + + /// + /// An enumerator for Zip entries + /// + private class ZipEntryEnumerator : IEnumerator + { + #region Constructors + + public ZipEntryEnumerator(ZipEntry[] entries) + { + array = entries; + } + + #endregion Constructors + + #region IEnumerator Members + + public object Current + { + get + { + return array[index]; + } + } + + public void Reset() + { + index = -1; + } + + public bool MoveNext() + { + return ++index < array.Length; + } + + #endregion IEnumerator Members + + #region Instance Fields + + private readonly ZipEntry[] array; + private int index = -1; + + #endregion Instance Fields + } + + /// + /// An is a stream that you can write uncompressed data + /// to and flush, but cannot read, seek or do anything else to. + /// + private class UncompressedStream : Stream + { + #region Constructors + + public UncompressedStream(Stream baseStream) + { + baseStream_ = baseStream; + } + + #endregion Constructors + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + public override bool CanRead + { + get + { + return false; + } + } + + /// + /// Write any buffered data to underlying storage. + /// + public override void Flush() + { + baseStream_.Flush(); + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite + { + get + { + return baseStream_.CanWrite; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Get the length in bytes of the stream. + /// + public override long Length + { + get + { + return 0; + } + } + + /// + /// Gets or sets the position within the current stream. + /// + public override long Position + { + get + { + return baseStream_.Position; + } + set + { + throw new NotImplementedException(); + } + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// + /// The sum of offset and count is larger than the buffer length. + /// Methods were called after the stream was closed. + /// The stream does not support reading. + /// buffer is null. + /// An I/O error occurs. + /// offset or count is negative. + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + + /// + /// Sets the position within the current stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// + /// The new position within the current stream. + /// + /// An I/O error occurs. + /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. + /// An I/O error occurs. + /// Methods were called after the stream was closed. + public override void SetLength(long value) + { + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// An I/O error occurs. + /// The stream does not support writing. + /// Methods were called after the stream was closed. + /// buffer is null. + /// The sum of offset and count is greater than the buffer length. + /// offset or count is negative. + public override void Write(byte[] buffer, int offset, int count) + { + baseStream_.Write(buffer, offset, count); + } + + private readonly + + #region Instance Fields + + Stream baseStream_; + + #endregion Instance Fields + } + + /// + /// A is an + /// whose data is only a part or subsection of a file. + /// + private class PartialInputStream : Stream + { + #region Constructors + + /// + /// Initialise a new instance of the class. + /// + /// The containing the underlying stream to use for IO. + /// The start of the partial data. + /// The length of the partial data. + public PartialInputStream(ZipFile zipFile, long start, long length) + { + start_ = start; + length_ = length; + + // Although this is the only time the zipfile is used + // keeping a reference here prevents premature closure of + // this zip file and thus the baseStream_. + + // Code like this will cause apparently random failures depending + // on the size of the files and when garbage is collected. + // + // ZipFile z = new ZipFile (stream); + // Stream reader = z.GetInputStream(0); + // uses reader here.... + zipFile_ = zipFile; + baseStream_ = zipFile_.baseStream_; + readPos_ = start; + end_ = start + length; + } + + #endregion Constructors + + /// + /// Read a byte from this stream. + /// + /// Returns the byte read or -1 on end of stream. + public override int ReadByte() + { + if (readPos_ >= end_) + { + // -1 is the correct value at end of stream. + return -1; + } + + lock (baseStream_) + { + baseStream_.Seek(readPos_++, SeekOrigin.Begin); + return baseStream_.ReadByte(); + } + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// + /// The sum of offset and count is larger than the buffer length. + /// Methods were called after the stream was closed. + /// The stream does not support reading. + /// buffer is null. + /// An I/O error occurs. + /// offset or count is negative. + public override int Read(byte[] buffer, int offset, int count) + { + lock (baseStream_) + { + if (count > end_ - readPos_) + { + count = (int)(end_ - readPos_); + if (count == 0) + { + return 0; + } + } + // Protect against Stream implementations that throw away their buffer on every Seek + // (for example, Mono FileStream) + if (baseStream_.Position != readPos_) + { + baseStream_.Seek(readPos_, SeekOrigin.Begin); + } + var readCount = baseStream_.Read(buffer, offset, count); + if (readCount > 0) + { + readPos_ += readCount; + } + return readCount; + } + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// An I/O error occurs. + /// The stream does not support writing. + /// Methods were called after the stream was closed. + /// buffer is null. + /// The sum of offset and count is greater than the buffer length. + /// offset or count is negative. + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + /// When overridden in a derived class, sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. + /// An I/O error occurs. + /// Methods were called after the stream was closed. + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// When overridden in a derived class, sets the position within the current stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// + /// The new position within the current stream. + /// + /// An I/O error occurs. + /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + public override long Seek(long offset, SeekOrigin origin) + { + var newPos = readPos_; + + switch (origin) + { + case SeekOrigin.Begin: + newPos = start_ + offset; + break; + + case SeekOrigin.Current: + newPos = readPos_ + offset; + break; + + case SeekOrigin.End: + newPos = end_ + offset; + break; + } + + if (newPos < start_) + { + throw new ArgumentException("Negative position is invalid"); + } + + if (newPos > end_) + { + throw new IOException("Cannot seek past end"); + } + readPos_ = newPos; + return readPos_; + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + /// An I/O error occurs. + public override void Flush() + { + // Nothing to do. + } + + /// + /// Gets or sets the position within the current stream. + /// + /// + /// The current position within the stream. + /// An I/O error occurs. + /// The stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Position + { + get { return readPos_ - start_; } + set + { + var newPos = start_ + value; + + if (newPos < start_) + { + throw new ArgumentException("Negative position is invalid"); + } + + if (newPos > end_) + { + throw new InvalidOperationException("Cannot seek past end"); + } + readPos_ = newPos; + } + } + + /// + /// Gets the length in bytes of the stream. + /// + /// + /// A long value representing the length of the stream in bytes. + /// A class derived from Stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Length + { + get { return length_; } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + /// false + /// true if the stream supports writing; otherwise, false. + public override bool CanWrite + { + get { return false; } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + /// true + /// true if the stream supports seeking; otherwise, false. + public override bool CanSeek + { + get { return true; } + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + /// true. + /// true if the stream supports reading; otherwise, false. + public override bool CanRead + { + get { return true; } + } + + /// + /// Gets a value that determines whether the current stream can time out. + /// + /// + /// A value that determines whether the current stream can time out. + public override bool CanTimeout + { + get { return baseStream_.CanTimeout; } + } + + #region Instance Fields + + private readonly ZipFile zipFile_; + private readonly Stream baseStream_; + private readonly long start_; + private readonly long length_; + private long readPos_; + private readonly long end_; + + #endregion Instance Fields + } + + #endregion Support Classes +} + +#endregion ZipFile Class + +#region DataSources + +/// +/// Provides a static way to obtain a source of data for an entry. +/// +public interface IStaticDataSource +{ + /// + /// Get a source of data by creating a new stream. + /// + /// Returns a to use for compression input. + /// Ideally a new stream is created and opened to achieve this, to avoid locking problems. + Stream GetSource(); +} + +/// +/// Represents a source of data that can dynamically provide +/// multiple data sources based on the parameters passed. +/// +public interface IDynamicDataSource { - #region Keys Required Event Args - - /// - /// Arguments used with KeysRequiredEvent - /// - public class KeysRequiredEventArgs : EventArgs - { - #region Constructors - - /// - /// Initialise a new instance of - /// - /// The name of the file for which keys are required. - public KeysRequiredEventArgs(string name) - { - fileName = name; - } - - /// - /// Initialise a new instance of - /// - /// The name of the file for which keys are required. - /// The current key value. - public KeysRequiredEventArgs(string name, byte[] keyValue) - { - fileName = name; - key = keyValue; - } - - #endregion Constructors - - #region Properties - - /// - /// Gets the name of the file for which keys are required. - /// - public string FileName - { - get { return fileName; } - } - - /// - /// Gets or sets the key value - /// - public byte[] Key - { - get { return key; } - set { key = value; } - } - - #endregion Properties - - #region Instance Fields - - private readonly string fileName; - private byte[] key; - - #endregion Instance Fields - } - - #endregion Keys Required Event Args - - #region Test Definitions - - /// - /// The strategy to apply to testing. - /// - public enum TestStrategy - { - /// - /// Find the first error only. - /// - FindFirstError, - - /// - /// Find all possible errors. - /// - FindAllErrors, - } - - /// - /// The operation in progress reported by a during testing. - /// - /// TestArchive - public enum TestOperation - { - /// - /// Setting up testing. - /// - Initialising, - - /// - /// Testing an individual entries header - /// - EntryHeader, - - /// - /// Testing an individual entries data - /// - EntryData, - - /// - /// Testing an individual entry has completed. - /// - EntryComplete, - - /// - /// Running miscellaneous tests - /// - MiscellaneousTests, - - /// - /// Testing is complete - /// - Complete, - } - - /// - /// Status returned by during testing. - /// - /// TestArchive - public class TestStatus - { - #region Constructors - - /// - /// Initialise a new instance of - /// - /// The this status applies to. - public TestStatus(ZipFile file) - { - file_ = file; - } - - #endregion Constructors - - #region Properties - - /// - /// Get the current in progress. - /// - public TestOperation Operation - { - get { return operation_; } - } - - /// - /// Get the this status is applicable to. - /// - public ZipFile File - { - get { return file_; } - } - - /// - /// Get the current/last entry tested. - /// - public ZipEntry Entry - { - get { return entry_; } - } - - /// - /// Get the number of errors detected so far. - /// - public int ErrorCount - { - get { return errorCount_; } - } - - /// - /// Get the number of bytes tested so far for the current entry. - /// - public long BytesTested - { - get { return bytesTested_; } - } - - /// - /// Get a value indicating whether the last entry test was valid. - /// - public bool EntryValid - { - get { return entryValid_; } - } - - #endregion Properties - - #region Internal API - - internal void AddError() - { - errorCount_++; - entryValid_ = false; - } - - internal void SetOperation(TestOperation operation) - { - operation_ = operation; - } - - internal void SetEntry(ZipEntry entry) - { - entry_ = entry; - entryValid_ = true; - bytesTested_ = 0; - } - - internal void SetBytesTested(long value) - { - bytesTested_ = value; - } - - #endregion Internal API - - #region Instance Fields - - private readonly ZipFile file_; - private ZipEntry entry_; - private bool entryValid_; - private int errorCount_; - private long bytesTested_; - private TestOperation operation_; - - #endregion Instance Fields - } - - /// - /// Delegate invoked during testing if supplied indicating current progress and status. - /// - /// If the message is non-null an error has occured. If the message is null - /// the operation as found in status has started. - public delegate void ZipTestResultHandler(TestStatus status, string message); - - #endregion Test Definitions - - #region Update Definitions - - /// - /// The possible ways of applying updates to an archive. - /// - public enum FileUpdateMode - { - /// - /// Perform all updates on temporary files ensuring that the original file is saved. - /// - Safe, - - /// - /// Update the archive directly, which is faster but less safe. - /// - Direct, - } - - #endregion Update Definitions - - #region ZipFile Class - - /// - /// This class represents a Zip archive. You can ask for the contained - /// entries, or get an input stream for a file entry. The entry is - /// automatically decompressed. - /// - /// You can also update the archive adding or deleting entries. - /// - /// This class is thread safe for input: You can open input streams for arbitrary - /// entries in different threads. - ///
- ///
Author of the original java version : Jochen Hoenicke - ///
- /// - /// - /// using System; - /// using System.Text; - /// using System.Collections; - /// using System.IO; - /// - /// using MelonLoader.ICSharpCode.SharpZipLib.Zip; - /// - /// class MainClass - /// { - /// static public void Main(string[] args) - /// { - /// using (ZipFile zFile = new ZipFile(args[0])) { - /// Console.WriteLine("Listing of : " + zFile.Name); - /// Console.WriteLine(""); - /// Console.WriteLine("Raw Size Size Date Time Name"); - /// Console.WriteLine("-------- -------- -------- ------ ---------"); - /// foreach (ZipEntry e in zFile) { - /// if ( e.IsFile ) { - /// DateTime d = e.DateTime; - /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, - /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"), - /// e.Name); - /// } - /// } - /// } - /// } - /// } - /// - /// - public class ZipFile : IEnumerable, IDisposable - { - #region KeyHandling - - /// - /// Delegate for handling keys/password setting during compression/decompression. - /// - public delegate void KeysRequiredEventHandler( - object sender, - KeysRequiredEventArgs e - ); - - /// - /// Event handler for handling encryption keys. - /// - public KeysRequiredEventHandler KeysRequired; - - /// - /// Handles getting of encryption keys when required. - /// - /// The file for which encryption keys are required. - private void OnKeysRequired(string fileName) - { - if (KeysRequired != null) - { - var krea = new KeysRequiredEventArgs(fileName, key); - KeysRequired(this, krea); - key = krea.Key; - } - } - - /// - /// Get/set the encryption key value. - /// - private byte[] Key - { - get { return key; } - set { key = value; } - } - - /// - /// Password to be used for encrypting/decrypting files. - /// - /// Set to null if no password is required. - public string Password - { - set - { - if (string.IsNullOrEmpty(value)) - { - key = null; - } - else - { - key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(value)); - } - - rawPassword_ = value; - } - } - - /// - /// Get a value indicating whether encryption keys are currently available. - /// - private bool HaveKeys - { - get { return key != null; } - } - - #endregion KeyHandling - - #region Constructors - - /// - /// Opens a Zip file with the given name for reading. - /// - /// The name of the file to open. - /// The argument supplied is null. - /// - /// An i/o error occurs - /// - /// - /// The file doesn't contain a valid zip archive. - /// - public ZipFile(string name) - { - name_ = name ?? throw new ArgumentNullException(nameof(name)); - - baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); - isStreamOwner = true; - - try - { - ReadEntries(); - } - catch - { - DisposeInternal(true); - throw; - } - } - - /// - /// Opens a Zip file reading the given . - /// - /// The to read archive data from. - /// The supplied argument is null. - /// - /// An i/o error occurs. - /// - /// - /// The file doesn't contain a valid zip archive. - /// - public ZipFile(FileStream file) : - this(file, false) - { - - } - - /// - /// Opens a Zip file reading the given . - /// - /// The to read archive data from. - /// true to leave the file open when the ZipFile is disposed, false to dispose of it - /// The supplied argument is null. - /// - /// An i/o error occurs. - /// - /// - /// The file doesn't contain a valid zip archive. - /// - public ZipFile(FileStream file, bool leaveOpen) - { - if (file == null) - { - throw new ArgumentNullException(nameof(file)); - } - - if (!file.CanSeek) - { - throw new ArgumentException("Stream is not seekable", nameof(file)); - } - - baseStream_ = file; - name_ = file.Name; - isStreamOwner = !leaveOpen; - - try - { - ReadEntries(); - } - catch - { - DisposeInternal(true); - throw; - } - } - - /// - /// Opens a Zip file reading the given . - /// - /// The to read archive data from. - /// - /// An i/o error occurs - /// - /// - /// The stream doesn't contain a valid zip archive.
- ///
- /// - /// The stream doesnt support seeking. - /// - /// - /// The stream argument is null. - /// - public ZipFile(Stream stream) : - this(stream, false) - { - - } - - /// - /// Opens a Zip file reading the given . - /// - /// The to read archive data from. - /// true to leave the stream open when the ZipFile is disposed, false to dispose of it - /// - /// An i/o error occurs - /// - /// - /// The stream doesn't contain a valid zip archive.
- ///
- /// - /// The stream doesnt support seeking. - /// - /// - /// The stream argument is null. - /// - public ZipFile(Stream stream, bool leaveOpen) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (!stream.CanSeek) - { - throw new ArgumentException("Stream is not seekable", nameof(stream)); - } - - baseStream_ = stream; - isStreamOwner = !leaveOpen; - - if (baseStream_.Length > 0) - { - try - { - ReadEntries(); - } - catch - { - DisposeInternal(true); - throw; - } - } - else - { - entries_ = Empty.Array(); - isNewArchive_ = true; - } - } - - /// - /// Initialises a default instance with no entries and no file storage. - /// - internal ZipFile() - { - entries_ = Empty.Array(); - isNewArchive_ = true; - } - - #endregion Constructors - - #region Destructors and Closing - - /// - /// Finalize this instance. - /// - ~ZipFile() - { - Dispose(false); - } - - /// - /// Closes the ZipFile. If the stream is owned then this also closes the underlying input stream. - /// Once closed, no further instance methods should be called. - /// - /// - /// An i/o error occurs. - /// - public void Close() - { - DisposeInternal(true); - GC.SuppressFinalize(this); - } - - #endregion Destructors and Closing - - #region Creators - - /// - /// Create a new whose data will be stored in a file. - /// - /// The name of the archive to create. - /// Returns the newly created - /// is null - public static ZipFile Create(string fileName) - { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - FileStream fs = File.Create(fileName); - - return new ZipFile - { - name_ = fileName, - baseStream_ = fs, - isStreamOwner = true - }; - } - - /// - /// Create a new whose data will be stored on a stream. - /// - /// The stream providing data storage. - /// Returns the newly created - /// is null - /// doesnt support writing. - public static ZipFile Create(Stream outStream) - { - if (outStream == null) - { - throw new ArgumentNullException(nameof(outStream)); - } - - if (!outStream.CanWrite) - { - throw new ArgumentException("Stream is not writeable", nameof(outStream)); - } - - if (!outStream.CanSeek) - { - throw new ArgumentException("Stream is not seekable", nameof(outStream)); - } - - var result = new ZipFile - { - baseStream_ = outStream - }; - return result; - } - - #endregion Creators - - #region Properties - - /// - /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance. - /// If the flag is true then the stream will be closed when Close is called. - /// - /// - /// The default value is true in all cases. - /// - public bool IsStreamOwner - { - get { return isStreamOwner; } - set { isStreamOwner = value; } - } - - /// - /// Get a value indicating whether - /// this archive is embedded in another file or not. - /// - public bool IsEmbeddedArchive - { - // Not strictly correct in all circumstances currently - get { return offsetOfFirstEntry > 0; } - } - - /// - /// Get a value indicating that this archive is a new one. - /// - public bool IsNewArchive - { - get { return isNewArchive_; } - } - - /// - /// Gets the comment for the zip file. - /// - public string ZipFileComment - { - get { return comment_; } - } - - /// - /// Gets the name of this zip file. - /// - public string Name - { - get { return name_; } - } - - /// - /// Gets the number of entries in this zip file. - /// - /// - /// The Zip file has been closed. - /// - [Obsolete("Use the Count property instead")] - public int Size - { - get - { - return entries_.Length; - } - } - - /// - /// Get the number of entries contained in this . - /// - public long Count - { - get - { - return entries_.Length; - } - } - - /// - /// Indexer property for ZipEntries - /// - [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")] - public ZipEntry this[int index] - { - get - { - return (ZipEntry)entries_[index].Clone(); - } - } - - #endregion Properties - - #region Input Handling - - /// - /// Gets an enumerator for the Zip entries in this Zip file. - /// - /// Returns an for this archive. - /// - /// The Zip file has been closed. - /// - public IEnumerator GetEnumerator() - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - return new ZipEntryEnumerator(entries_); - } - - /// - /// Return the index of the entry with a matching name - /// - /// Entry name to find - /// If true the comparison is case insensitive - /// The index position of the matching entry or -1 if not found - /// - /// The Zip file has been closed. - /// - public int FindEntry(string name, bool ignoreCase) - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - // TODO: This will be slow as the next ice age for huge archives! - for (int i = 0; i < entries_.Length; i++) - { - if (string.Compare(name, entries_[i].Name, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0) - { - return i; - } - } - return -1; - } - - /// - /// Searches for a zip entry in this archive with the given name. - /// String comparisons are case insensitive - /// - /// - /// The name to find. May contain directory components separated by slashes ('/'). - /// - /// - /// A clone of the zip entry, or null if no entry with that name exists. - /// - /// - /// The Zip file has been closed. - /// - public ZipEntry GetEntry(string name) - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - int index = FindEntry(name, true); - return (index >= 0) ? (ZipEntry)entries_[index].Clone() : null; - } - - /// - /// Gets an input stream for reading the given zip entry data in an uncompressed form. - /// Normally the should be an entry returned by GetEntry(). - /// - /// The to obtain a data for - /// An input containing data for this - /// - /// The ZipFile has already been closed - /// - /// - /// The compression method for the entry is unknown - /// - /// - /// The entry is not found in the ZipFile - /// - public Stream GetInputStream(ZipEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - long index = entry.ZipFileIndex; - if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) - { - index = FindEntry(entry.Name, true); - if (index < 0) - { - throw new ZipException("Entry cannot be found"); - } - } - return GetInputStream(index); - } - - /// - /// Creates an input stream reading a zip entry - /// - /// The index of the entry to obtain an input stream for. - /// - /// An input containing data for this - /// - /// - /// The ZipFile has already been closed - /// - /// - /// The compression method for the entry is unknown - /// - /// - /// The entry is not found in the ZipFile - /// - public Stream GetInputStream(long entryIndex) - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - long start = LocateEntry(entries_[entryIndex]); - CompressionMethod method = entries_[entryIndex].CompressionMethod; - Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize); - - if (entries_[entryIndex].IsCrypted == true) - { - result = CreateAndInitDecryptionStream(result, entries_[entryIndex]); - if (result == null) - { - throw new ZipException("Unable to decrypt this entry"); - } - } - - switch (method) - { - case CompressionMethod.Stored: - // read as is. - break; - - case CompressionMethod.Deflated: - // No need to worry about ownership and closing as underlying stream close does nothing. - result = new InflaterInputStream(result, new Inflater(true)); - break; - - case CompressionMethod.BZip2: - result = new BZip2.BZip2InputStream(result); - break; - - default: - throw new ZipException("Unsupported compression method " + method); - } - - return result; - } - - #endregion Input Handling - - #region Archive Testing - - /// - /// Test an archive for integrity/validity - /// - /// Perform low level data Crc check - /// true if all tests pass, false otherwise - /// Testing will terminate on the first error found. - public bool TestArchive(bool testData) - { - return TestArchive(testData, TestStrategy.FindFirstError, null); - } - - /// - /// Test an archive for integrity/validity - /// - /// Perform low level data Crc check - /// The to apply. - /// The handler to call during testing. - /// true if all tests pass, false otherwise - /// The object has already been closed. - public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler) - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - var status = new TestStatus(this); - - resultHandler?.Invoke(status, null); - - HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header; - - bool testing = true; - - try - { - int entryIndex = 0; - - while (testing && (entryIndex < Count)) - { - if (resultHandler != null) - { - status.SetEntry(this[entryIndex]); - status.SetOperation(TestOperation.EntryHeader); - resultHandler(status, null); - } - - try - { - TestLocalHeader(this[entryIndex], test); - } - catch (ZipException ex) - { - status.AddError(); - - resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'"); - - testing &= strategy != TestStrategy.FindFirstError; - } - - if (testing && testData && this[entryIndex].IsFile) - { - // Don't check CRC for AES encrypted archives - var checkCRC = this[entryIndex].AESKeySize == 0; - - if (resultHandler != null) - { - status.SetOperation(TestOperation.EntryData); - resultHandler(status, null); - } - - var crc = new Crc32(); - - using (Stream entryStream = this.GetInputStream(this[entryIndex])) - { - byte[] buffer = new byte[4096]; - long totalBytes = 0; - int bytesRead; - while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) - { - if (checkCRC) - { - crc.Update(new ArraySegment(buffer, 0, bytesRead)); - } - - if (resultHandler != null) - { - totalBytes += bytesRead; - status.SetBytesTested(totalBytes); - resultHandler(status, null); - } - } - } - - if (checkCRC && this[entryIndex].Crc != crc.Value) - { - status.AddError(); - - resultHandler?.Invoke(status, "CRC mismatch"); - - testing &= strategy != TestStrategy.FindFirstError; - } - - if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) - { - var helper = new ZipHelperStream(baseStream_); - var data = new DescriptorData(); - helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data); - - if (checkCRC && this[entryIndex].Crc != data.Crc) - { - status.AddError(); - resultHandler?.Invoke(status, "Descriptor CRC mismatch"); - } - - if (this[entryIndex].CompressedSize != data.CompressedSize) - { - status.AddError(); - resultHandler?.Invoke(status, "Descriptor compressed size mismatch"); - } - - if (this[entryIndex].Size != data.Size) - { - status.AddError(); - resultHandler?.Invoke(status, "Descriptor size mismatch"); - } - } - } - - if (resultHandler != null) - { - status.SetOperation(TestOperation.EntryComplete); - resultHandler(status, null); - } - - entryIndex += 1; - } - - if (resultHandler != null) - { - status.SetOperation(TestOperation.MiscellaneousTests); - resultHandler(status, null); - } - - // TODO: the 'Corrina Johns' test where local headers are missing from - // the central directory. They are therefore invisible to many archivers. - } - catch (Exception ex) - { - status.AddError(); - - resultHandler?.Invoke(status, $"Exception during test - '{ex.Message}'"); - } - - if (resultHandler != null) - { - status.SetOperation(TestOperation.Complete); - status.SetEntry(null); - resultHandler(status, null); - } - - return (status.ErrorCount == 0); - } - - [Flags] - private enum HeaderTest - { - Extract = 0x01, // Check that this header represents an entry whose data can be extracted - Header = 0x02, // Check that this header contents are valid - } - - /// - /// Test a local header against that provided from the central directory - /// - /// - /// The entry to test against - /// - /// The type of tests to carry out. - /// The offset of the entries data in the file - private long TestLocalHeader(ZipEntry entry, HeaderTest tests) - { - lock (baseStream_) - { - bool testHeader = (tests & HeaderTest.Header) != 0; - bool testData = (tests & HeaderTest.Extract) != 0; - - var entryAbsOffset = offsetOfFirstEntry + entry.Offset; - - baseStream_.Seek(entryAbsOffset, SeekOrigin.Begin); - var signature = (int)ReadLEUint(); - - if (signature != ZipConstants.LocalHeaderSignature) - { - throw new ZipException(string.Format("Wrong local header signature at 0x{0:x}, expected 0x{1:x8}, actual 0x{2:x8}", - entryAbsOffset, ZipConstants.LocalHeaderSignature, signature)); - } - - var extractVersion = (short)(ReadLEUshort() & 0x00ff); - var localFlags = (short)ReadLEUshort(); - var compressionMethod = (short)ReadLEUshort(); - var fileTime = (short)ReadLEUshort(); - var fileDate = (short)ReadLEUshort(); - uint crcValue = ReadLEUint(); - long compressedSize = ReadLEUint(); - long size = ReadLEUint(); - int storedNameLength = ReadLEUshort(); - int extraDataLength = ReadLEUshort(); - - byte[] nameData = new byte[storedNameLength]; - StreamUtils.ReadFully(baseStream_, nameData); - - byte[] extraData = new byte[extraDataLength]; - StreamUtils.ReadFully(baseStream_, extraData); - - var localExtraData = new ZipExtraData(extraData); - - // Extra data / zip64 checks - if (localExtraData.Find(1)) - { - // 2010-03-04 Forum 10512: removed checks for version >= ZipConstants.VersionZip64 - // and size or compressedSize = MaxValue, due to rogue creators. - - size = localExtraData.ReadLong(); - compressedSize = localExtraData.ReadLong(); - - if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) - { - // These may be valid if patched later - if ((size != -1) && (size != entry.Size)) - { - throw new ZipException("Size invalid for descriptor"); - } - - if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) - { - throw new ZipException("Compressed size invalid for descriptor"); - } - } - } - else - { - // No zip64 extra data but entry requires it. - if ((extractVersion >= ZipConstants.VersionZip64) && - (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) - { - throw new ZipException("Required Zip64 extended information missing"); - } - } - - if (testData) - { - if (entry.IsFile) - { - if (!entry.IsCompressionMethodSupported()) - { - throw new ZipException("Compression method not supported"); - } - - if ((extractVersion > ZipConstants.VersionMadeBy) - || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) - { - throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion)); - } - - if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0) - { - throw new ZipException("The library does not support the zip version required to extract this entry"); - } - } - } - - if (testHeader) - { - if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. - (extractVersion != 10) && - (extractVersion != 11) && - (extractVersion != 20) && - (extractVersion != 21) && - (extractVersion != 25) && - (extractVersion != 27) && - (extractVersion != 45) && - (extractVersion != 46) && - (extractVersion != 50) && - (extractVersion != 51) && - (extractVersion != 52) && - (extractVersion != 61) && - (extractVersion != 62) && - (extractVersion != 63) - ) - { - throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion)); - } - - // Local entry flags dont have reserved bit set on. - if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0) - { - throw new ZipException("Reserved bit flags cannot be set."); - } - - // Encryption requires extract version >= 20 - if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) - { - throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); - } - - // Strong encryption requires encryption flag to be set and extract version >= 50. - if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) - { - if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) - { - throw new ZipException("Strong encryption flag set but encryption flag is not set"); - } - - if (extractVersion < 50) - { - throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); - } - } - - // Patched entries require extract version >= 27 - if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) - { - throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); - } - - // Central header flags match local entry flags. - if (localFlags != entry.Flags) - { - throw new ZipException("Central header/local header flags mismatch"); - } - - // Central header compression method matches local entry - if (entry.CompressionMethodForHeader != (CompressionMethod)compressionMethod) - { - throw new ZipException("Central header/local header compression method mismatch"); - } - - if (entry.Version != extractVersion) - { - throw new ZipException("Extract version mismatch"); - } - - // Strong encryption and extract version match - if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) - { - if (extractVersion < 62) - { - throw new ZipException("Strong encryption flag set but version not high enough"); - } - } - - if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) - { - if ((fileTime != 0) || (fileDate != 0)) - { - throw new ZipException("Header masked set but date/time values non-zero"); - } - } - - if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) - { - if (crcValue != (uint)entry.Crc) - { - throw new ZipException("Central header/local header crc mismatch"); - } - } - - // Crc valid for empty entry. - // This will also apply to streamed entries where size isnt known and the header cant be patched - if ((size == 0) && (compressedSize == 0)) - { - if (crcValue != 0) - { - throw new ZipException("Invalid CRC for empty entry"); - } - } - - // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings - // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably - if (entry.Name.Length > storedNameLength) - { - throw new ZipException("File name length mismatch"); - } - - // Name data has already been read convert it and compare. - string localName = ZipStrings.ConvertToStringExt(localFlags, nameData); - - // Central directory and local entry name match - if (localName != entry.Name) - { - throw new ZipException("Central header and local header file name mismatch"); - } - - // Directories have zero actual size but can have compressed size - if (entry.IsDirectory) - { - if (size > 0) - { - throw new ZipException("Directory cannot have size"); - } - - // There may be other cases where the compressed size can be greater than this? - // If so until details are known we will be strict. - if (entry.IsCrypted) - { - if (compressedSize > entry.EncryptionOverheadSize + 2) - { - throw new ZipException("Directory compressed size invalid"); - } - } - else if (compressedSize > 2) - { - // When not compressed the directory size can validly be 2 bytes - // if the true size wasn't known when data was originally being written. - // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes - throw new ZipException("Directory compressed size invalid"); - } - } - - if (!ZipNameTransform.IsValidName(localName, true)) - { - throw new ZipException("Name is invalid"); - } - } - - // Tests that apply to both data and header. - - // Size can be verified only if it is known in the local header. - // it will always be known in the central header. - if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) || - ((size > 0 || compressedSize > 0) && entry.Size > 0)) - { - if ((size != 0) - && (size != entry.Size)) - { - throw new ZipException( - string.Format("Size mismatch between central header({0}) and local header({1})", - entry.Size, size)); - } - - if ((compressedSize != 0) - && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) - { - throw new ZipException( - string.Format("Compressed size mismatch between central header({0}) and local header({1})", - entry.CompressedSize, compressedSize)); - } - } - - int extraLength = storedNameLength + extraDataLength; - return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; - } - } - - #endregion Archive Testing - - #region Updating - - private const int DefaultBufferSize = 4096; - - /// - /// The kind of update to apply. - /// - private enum UpdateCommand - { - Copy, // Copy original file contents. - Modify, // Change encryption, compression, attributes, name, time etc, of an existing file. - Add, // Add a new file to the archive. - } - - #region Properties - - /// - /// Get / set the to apply to names when updating. - /// - public INameTransform NameTransform - { - get - { - return updateEntryFactory_.NameTransform; - } - - set - { - updateEntryFactory_.NameTransform = value; - } - } - - /// - /// Get/set the used to generate values - /// during updates. - /// - public IEntryFactory EntryFactory - { - get - { - return updateEntryFactory_; - } - - set - { - if (value == null) - { - updateEntryFactory_ = new ZipEntryFactory(); - } - else - { - updateEntryFactory_ = value; - } - } - } - - /// - /// Get /set the buffer size to be used when updating this zip file. - /// - public int BufferSize - { - get { return bufferSize_; } - set - { - if (value < 1024) - { - throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024"); - } - - if (bufferSize_ != value) - { - bufferSize_ = value; - copyBuffer_ = null; - } - } - } - - /// - /// Get a value indicating an update has been started. - /// - public bool IsUpdating - { - get { return updates_ != null; } - } - - /// - /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. - /// - public UseZip64 UseZip64 - { - get { return useZip64_; } - set { useZip64_ = value; } - } - - #endregion Properties - - #region Immediate updating - - // TBD: Direct form of updating - // - // public void Update(IEntryMatcher deleteMatcher) - // { - // } - // - // public void Update(IScanner addScanner) - // { - // } - - #endregion Immediate updating - - #region Deferred Updating - - /// - /// Begin updating this archive. - /// - /// The archive storage for use during the update. - /// The data source to utilise during updating. - /// ZipFile has been closed. - /// One of the arguments provided is null - /// ZipFile has been closed. - public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - if (IsEmbeddedArchive) - { - throw new ZipException("Cannot update embedded/SFX archives"); - } - - archiveStorage_ = archiveStorage ?? throw new ArgumentNullException(nameof(archiveStorage)); - updateDataSource_ = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); - - // NOTE: the baseStream_ may not currently support writing or seeking. - - updateIndex_ = new Dictionary(); - - updates_ = new List(entries_.Length); - foreach (ZipEntry entry in entries_) - { - int index = updates_.Count; - updates_.Add(new ZipUpdate(entry)); - updateIndex_.Add(entry.Name, index); - } - - // We must sort by offset before using offset's calculated sizes - updates_.Sort(new UpdateComparer()); - - int idx = 0; - foreach (ZipUpdate update in updates_) - { - //If last entry, there is no next entry offset to use - if (idx == updates_.Count - 1) - break; - - update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset; - idx++; - } - updateCount_ = updates_.Count; - - contentsEdited_ = false; - commentEdited_ = false; - newComment_ = null; - } - - /// - /// Begin updating to this archive. - /// - /// The storage to use during the update. - public void BeginUpdate(IArchiveStorage archiveStorage) - { - BeginUpdate(archiveStorage, new DynamicDiskDataSource()); - } - - /// - /// Begin updating this archive. - /// - /// - /// - /// - public void BeginUpdate() - { - if (Name == null) - { - BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource()); - } - else - { - BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource()); - } - } - - /// - /// Commit current updates, updating this archive. - /// - /// - /// - /// ZipFile has been closed. - public void CommitUpdate() - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - CheckUpdating(); - - try - { - updateIndex_.Clear(); - updateIndex_ = null; - - if (contentsEdited_) - { - RunUpdates(); - } - else if (commentEdited_) - { - UpdateCommentOnly(); - } - else - { - // Create an empty archive if none existed originally. - if (entries_.Length == 0) - { - byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_); - using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) - { - zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment); - } - } - } - } - finally - { - PostUpdateCleanup(); - } - } - - /// - /// Abort updating leaving the archive unchanged. - /// - /// - /// - public void AbortUpdate() - { - PostUpdateCleanup(); - } - - /// - /// Set the file comment to be recorded when the current update is commited. - /// - /// The comment to record. - /// ZipFile has been closed. - public void SetComment(string comment) - { - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - CheckUpdating(); - - newComment_ = new ZipString(comment); - - if (newComment_.RawLength > 0xffff) - { - newComment_ = null; - throw new ZipException("Comment length exceeds maximum - 65535"); - } - - // We dont take account of the original and current comment appearing to be the same - // as encoding may be different. - commentEdited_ = true; - } - - #endregion Deferred Updating - - #region Adding Entries - - private void AddUpdate(ZipUpdate update) - { - contentsEdited_ = true; - - int index = FindExistingUpdate(update.Entry.Name, isEntryName: true); - - if (index >= 0) - { - if (updates_[index] == null) - { - updateCount_ += 1; - } - - // Direct replacement is faster than delete and add. - updates_[index] = update; - } - else - { - index = updates_.Count; - updates_.Add(update); - updateCount_ += 1; - updateIndex_.Add(update.Entry.Name, index); - } - } - - /// - /// Add a new entry to the archive. - /// - /// The name of the file to add. - /// The compression method to use. - /// Ensure Unicode text is used for name and comment for this entry. - /// Argument supplied is null. - /// ZipFile has been closed. - /// Compression method is not supported for creating entries. - public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText) - { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - if (isDisposed_) - { - throw new ObjectDisposedException("ZipFile"); - } - - CheckSupportedCompressionMethod(compressionMethod); - CheckUpdating(); - contentsEdited_ = true; - - ZipEntry entry = EntryFactory.MakeFileEntry(fileName); - entry.IsUnicodeText = useUnicodeText; - entry.CompressionMethod = compressionMethod; - - AddUpdate(new ZipUpdate(fileName, entry)); - } - - /// - /// Add a new entry to the archive. - /// - /// The name of the file to add. - /// The compression method to use. - /// ZipFile has been closed. - /// Compression method is not supported for creating entries. - public void Add(string fileName, CompressionMethod compressionMethod) - { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - CheckSupportedCompressionMethod(compressionMethod); - CheckUpdating(); - contentsEdited_ = true; - - ZipEntry entry = EntryFactory.MakeFileEntry(fileName); - entry.CompressionMethod = compressionMethod; - AddUpdate(new ZipUpdate(fileName, entry)); - } - - /// - /// Add a file to the archive. - /// - /// The name of the file to add. - /// Argument supplied is null. - public void Add(string fileName) - { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - CheckUpdating(); - AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName))); - } - - /// - /// Add a file to the archive. - /// - /// The name of the file to add. - /// The name to use for the on the Zip file created. - /// Argument supplied is null. - public void Add(string fileName, string entryName) - { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - if (entryName == null) - { - throw new ArgumentNullException(nameof(entryName)); - } - - CheckUpdating(); - AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true))); - } - - /// - /// Add a file entry with data. - /// - /// The source of the data for this entry. - /// The name to give to the entry. - public void Add(IStaticDataSource dataSource, string entryName) - { - if (dataSource == null) - { - throw new ArgumentNullException(nameof(dataSource)); - } - - if (entryName == null) - { - throw new ArgumentNullException(nameof(entryName)); - } - - CheckUpdating(); - AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false))); - } - - /// - /// Add a file entry with data. - /// - /// The source of the data for this entry. - /// The name to give to the entry. - /// The compression method to use. - /// Compression method is not supported for creating entries. - public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) - { - if (dataSource == null) - { - throw new ArgumentNullException(nameof(dataSource)); - } - - if (entryName == null) - { - throw new ArgumentNullException(nameof(entryName)); - } - - CheckSupportedCompressionMethod(compressionMethod); - CheckUpdating(); - - ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); - entry.CompressionMethod = compressionMethod; - - AddUpdate(new ZipUpdate(dataSource, entry)); - } - - /// - /// Add a file entry with data. - /// - /// The source of the data for this entry. - /// The name to give to the entry. - /// The compression method to use. - /// Ensure Unicode text is used for name and comments for this entry. - /// Compression method is not supported for creating entries. - public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicodeText) - { - if (dataSource == null) - { - throw new ArgumentNullException(nameof(dataSource)); - } - - if (entryName == null) - { - throw new ArgumentNullException(nameof(entryName)); - } - - CheckSupportedCompressionMethod(compressionMethod); - CheckUpdating(); - - ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false); - entry.IsUnicodeText = useUnicodeText; - entry.CompressionMethod = compressionMethod; - - AddUpdate(new ZipUpdate(dataSource, entry)); - } - - /// - /// Add a that contains no data. - /// - /// The entry to add. - /// This can be used to add directories, volume labels, or empty file entries. - public void Add(ZipEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - CheckUpdating(); - - if ((entry.Size != 0) || (entry.CompressedSize != 0)) - { - throw new ZipException("Entry cannot have any data"); - } - - AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); - } - - /// - /// Add a with data. - /// - /// The source of the data for this entry. - /// The entry to add. - /// This can be used to add file entries with a custom data source. - /// - /// The encryption method specified in is unsupported. - /// - /// Compression method is not supported for creating entries. - public void Add(IStaticDataSource dataSource, ZipEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - if (dataSource == null) - { - throw new ArgumentNullException(nameof(dataSource)); - } - - // We don't currently support adding entries with AES encryption, so throw - // up front instead of failing or falling back to ZipCrypto later on - if (entry.AESKeySize > 0) - { - throw new NotSupportedException("Creation of AES encrypted entries is not supported"); - } - - CheckSupportedCompressionMethod(entry.CompressionMethod); - CheckUpdating(); - - AddUpdate(new ZipUpdate(dataSource, entry)); - } - - /// - /// Add a directory entry to the archive. - /// - /// The directory to add. - public void AddDirectory(string directoryName) - { - if (directoryName == null) - { - throw new ArgumentNullException(nameof(directoryName)); - } - - CheckUpdating(); - - ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName); - AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry)); - } - - /// - /// Check if the specified compression method is supported for adding a new entry. - /// - /// The compression method for the new entry. - private static void CheckSupportedCompressionMethod(CompressionMethod compressionMethod) - { - if (compressionMethod != CompressionMethod.Deflated && compressionMethod != CompressionMethod.Stored && compressionMethod != CompressionMethod.BZip2) - { - throw new NotImplementedException("Compression method not supported"); - } - } - - #endregion Adding Entries - - #region Modifying Entries - - /* Modify not yet ready for public consumption. - Direct modification of an entry should not overwrite original data before its read. - Safe mode is trivial in this sense. - public void Modify(ZipEntry original, ZipEntry updated) - { - if ( original == null ) { - throw new ArgumentNullException("original"); - } - if ( updated == null ) { - throw new ArgumentNullException("updated"); - } - CheckUpdating(); - contentsEdited_ = true; - updates_.Add(new ZipUpdate(original, updated)); - } - */ - - #endregion Modifying Entries - - #region Deleting Entries - - /// - /// Delete an entry by name - /// - /// The filename to delete - /// True if the entry was found and deleted; false otherwise. - public bool Delete(string fileName) - { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - CheckUpdating(); - - bool result = false; - int index = FindExistingUpdate(fileName); - if ((index >= 0) && (updates_[index] != null)) - { - result = true; - contentsEdited_ = true; - updates_[index] = null; - updateCount_ -= 1; - } - else - { - throw new ZipException("Cannot find entry to delete"); - } - return result; - } - - /// - /// Delete a from the archive. - /// - /// The entry to delete. - public void Delete(ZipEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - CheckUpdating(); - - int index = FindExistingUpdate(entry); - if (index >= 0) - { - contentsEdited_ = true; - updates_[index] = null; - updateCount_ -= 1; - } - else - { - throw new ZipException("Cannot find entry to delete"); - } - } - - #endregion Deleting Entries - - #region Update Support - - #region Writing Values/Headers - - private void WriteLEShort(int value) - { - baseStream_.WriteByte((byte)(value & 0xff)); - baseStream_.WriteByte((byte)((value >> 8) & 0xff)); - } - - /// - /// Write an unsigned short in little endian byte order. - /// - private void WriteLEUshort(ushort value) - { - baseStream_.WriteByte((byte)(value & 0xff)); - baseStream_.WriteByte((byte)(value >> 8)); - } - - /// - /// Write an int in little endian byte order. - /// - private void WriteLEInt(int value) - { - WriteLEShort(value & 0xffff); - WriteLEShort(value >> 16); - } - - /// - /// Write an unsigned int in little endian byte order. - /// - private void WriteLEUint(uint value) - { - WriteLEUshort((ushort)(value & 0xffff)); - WriteLEUshort((ushort)(value >> 16)); - } - - /// - /// Write a long in little endian byte order. - /// - private void WriteLeLong(long value) - { - WriteLEInt((int)(value & 0xffffffff)); - WriteLEInt((int)(value >> 32)); - } - - private void WriteLEUlong(ulong value) - { - WriteLEUint((uint)(value & 0xffffffff)); - WriteLEUint((uint)(value >> 32)); - } - - private void WriteLocalEntryHeader(ZipUpdate update) - { - ZipEntry entry = update.OutEntry; - - // TODO: Local offset will require adjusting for multi-disk zip files. - entry.Offset = baseStream_.Position; - - // TODO: Need to clear any entry flags that dont make sense or throw an exception here. - if (update.Command != UpdateCommand.Copy) - { - if (entry.CompressionMethod == CompressionMethod.Deflated) - { - if (entry.Size == 0) - { - // No need to compress - no data. - entry.CompressedSize = entry.Size; - entry.Crc = 0; - entry.CompressionMethod = CompressionMethod.Stored; - } - } - else if (entry.CompressionMethod == CompressionMethod.Stored) - { - entry.Flags &= ~(int)GeneralBitFlags.Descriptor; - } - - if (HaveKeys) - { - entry.IsCrypted = true; - if (entry.Crc < 0) - { - entry.Flags |= (int)GeneralBitFlags.Descriptor; - } - } - else - { - entry.IsCrypted = false; - } - - switch (useZip64_) - { - case UseZip64.Dynamic: - if (entry.Size < 0) - { - entry.ForceZip64(); - } - break; - - case UseZip64.On: - entry.ForceZip64(); - break; - - case UseZip64.Off: - // Do nothing. The entry itself may be using Zip64 independently. - break; - } - } - - // Write the local file header - WriteLEInt(ZipConstants.LocalHeaderSignature); - - WriteLEShort(entry.Version); - WriteLEShort(entry.Flags); - - WriteLEShort((byte)entry.CompressionMethodForHeader); - WriteLEInt((int)entry.DosTime); - - if (!entry.HasCrc) - { - // Note patch address for updating CRC later. - update.CrcPatchOffset = baseStream_.Position; - WriteLEInt((int)0); - } - else - { - WriteLEInt(unchecked((int)entry.Crc)); - } - - if (entry.LocalHeaderRequiresZip64) - { - WriteLEInt(-1); - WriteLEInt(-1); - } - else - { - if ((entry.CompressedSize < 0) || (entry.Size < 0)) - { - update.SizePatchOffset = baseStream_.Position; - } - - WriteLEInt((int)entry.CompressedSize); - WriteLEInt((int)entry.Size); - } - - byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); - - if (name.Length > 0xFFFF) - { - throw new ZipException("Entry name too long."); - } - - var ed = new ZipExtraData(entry.ExtraData); - - if (entry.LocalHeaderRequiresZip64) - { - ed.StartNewEntry(); - - // Local entry header always includes size and compressed size. - // NOTE the order of these fields is reversed when compared to the normal headers! - ed.AddLeLong(entry.Size); - ed.AddLeLong(entry.CompressedSize); - ed.AddNewEntry(1); - } - else - { - ed.Delete(1); - } - - entry.ExtraData = ed.GetEntryData(); - - WriteLEShort(name.Length); - WriteLEShort(entry.ExtraData.Length); - - if (name.Length > 0) - { - baseStream_.Write(name, 0, name.Length); - } - - if (entry.LocalHeaderRequiresZip64) - { - if (!ed.Find(1)) - { - throw new ZipException("Internal error cannot find extra data"); - } - - update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex; - } - - if (entry.ExtraData.Length > 0) - { - baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length); - } - } - - private int WriteCentralDirectoryHeader(ZipEntry entry) - { - if (entry.CompressedSize < 0) - { - throw new ZipException("Attempt to write central directory entry with unknown csize"); - } - - if (entry.Size < 0) - { - throw new ZipException("Attempt to write central directory entry with unknown size"); - } - - if (entry.Crc < 0) - { - throw new ZipException("Attempt to write central directory entry with unknown crc"); - } - - // Write the central file header - WriteLEInt(ZipConstants.CentralHeaderSignature); - - // Version made by - WriteLEShort((entry.HostSystem << 8) | entry.VersionMadeBy); - - // Version required to extract - WriteLEShort(entry.Version); - - WriteLEShort(entry.Flags); - - unchecked - { - WriteLEShort((byte)entry.CompressionMethodForHeader); - WriteLEInt((int)entry.DosTime); - WriteLEInt((int)entry.Crc); - } - - bool useExtraCompressedSize = false; //Do we want to store the compressed size in the extra data? - if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) - { - useExtraCompressedSize = true; - WriteLEInt(-1); - } - else - { - WriteLEInt((int)(entry.CompressedSize & 0xffffffff)); - } - - bool useExtraUncompressedSize = false; //Do we want to store the uncompressed size in the extra data? - if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) - { - useExtraUncompressedSize = true; - WriteLEInt(-1); - } - else - { - WriteLEInt((int)entry.Size); - } - - byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); - - if (name.Length > 0xFFFF) - { - throw new ZipException("Entry name is too long."); - } - - WriteLEShort(name.Length); - - // Central header extra data is different to local header version so regenerate. - var ed = new ZipExtraData(entry.ExtraData); - - if (entry.CentralHeaderRequiresZip64) - { - ed.StartNewEntry(); - - if (useExtraUncompressedSize) - { - ed.AddLeLong(entry.Size); - } - - if (useExtraCompressedSize) - { - ed.AddLeLong(entry.CompressedSize); - } - - if (entry.Offset >= 0xffffffff) - { - ed.AddLeLong(entry.Offset); - } - - // Number of disk on which this file starts isnt supported and is never written here. - ed.AddNewEntry(1); - } - else - { - // Should have already be done when local header was added. - ed.Delete(1); - } - - byte[] centralExtraData = ed.GetEntryData(); - - WriteLEShort(centralExtraData.Length); - WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); - - WriteLEShort(0); // disk number - WriteLEShort(0); // internal file attributes - - // External file attributes... - if (entry.ExternalFileAttributes != -1) - { - WriteLEInt(entry.ExternalFileAttributes); - } - else - { - if (entry.IsDirectory) - { - WriteLEUint(16); - } - else - { - WriteLEUint(0); - } - } - - if (entry.Offset >= 0xffffffff) - { - WriteLEUint(0xffffffff); - } - else - { - WriteLEUint((uint)(int)entry.Offset); - } - - if (name.Length > 0) - { - baseStream_.Write(name, 0, name.Length); - } - - if (centralExtraData.Length > 0) - { - baseStream_.Write(centralExtraData, 0, centralExtraData.Length); - } - - byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : Empty.Array(); - - if (rawComment.Length > 0) - { - baseStream_.Write(rawComment, 0, rawComment.Length); - } - - return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; - } - - #endregion Writing Values/Headers - - private void PostUpdateCleanup() - { - updateDataSource_ = null; - updates_ = null; - updateIndex_ = null; - - if (archiveStorage_ != null) - { - archiveStorage_.Dispose(); - archiveStorage_ = null; - } - } - - private string GetTransformedFileName(string name) - { - INameTransform transform = NameTransform; - return (transform != null) ? - transform.TransformFile(name) : - name; - } - - private string GetTransformedDirectoryName(string name) - { - INameTransform transform = NameTransform; - return (transform != null) ? - transform.TransformDirectory(name) : - name; - } - - /// - /// Get a raw memory buffer. - /// - /// Returns a raw memory buffer. - private byte[] GetBuffer() - { - if (copyBuffer_ == null) - { - copyBuffer_ = new byte[bufferSize_]; - } - return copyBuffer_; - } - - private void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source) - { - // Don't include the signature size to allow copy without seeking - var bytesToCopy = GetDescriptorSize(update, false); - - // Don't touch the source stream if no descriptor is present - if (bytesToCopy == 0) return; - - var buffer = GetBuffer(); - - // Copy the first 4 bytes of the descriptor - source.Read(buffer, 0, sizeof(int)); - dest.Write(buffer, 0, sizeof(int)); - - if (BitConverter.ToUInt32(buffer, 0) != ZipConstants.DataDescriptorSignature) - { - // The initial bytes wasn't the descriptor, reduce the pending byte count - bytesToCopy -= buffer.Length; - } - - while (bytesToCopy > 0) - { - int readSize = Math.Min(buffer.Length, bytesToCopy); - - int bytesRead = source.Read(buffer, 0, readSize); - if (bytesRead > 0) - { - dest.Write(buffer, 0, bytesRead); - bytesToCopy -= bytesRead; - } - else - { - throw new ZipException("Unxpected end of stream"); - } - } - } - - private void CopyBytes(ZipUpdate update, Stream destination, Stream source, - long bytesToCopy, bool updateCrc) - { - if (destination == source) - { - throw new InvalidOperationException("Destination and source are the same"); - } - - // NOTE: Compressed size is updated elsewhere. - var crc = new Crc32(); - byte[] buffer = GetBuffer(); - - long targetBytes = bytesToCopy; - long totalBytesRead = 0; - - int bytesRead; - do - { - int readSize = buffer.Length; - - if (bytesToCopy < readSize) - { - readSize = (int)bytesToCopy; - } - - bytesRead = source.Read(buffer, 0, readSize); - if (bytesRead > 0) - { - if (updateCrc) - { - crc.Update(new ArraySegment(buffer, 0, bytesRead)); - } - destination.Write(buffer, 0, bytesRead); - bytesToCopy -= bytesRead; - totalBytesRead += bytesRead; - } - } - while ((bytesRead > 0) && (bytesToCopy > 0)); - - if (totalBytesRead != targetBytes) - { - throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); - } - - if (updateCrc) - { - update.OutEntry.Crc = crc.Value; - } - } - - /// - /// Get the size of the source descriptor for a . - /// - /// The update to get the size for. - /// Whether to include the signature size - /// The descriptor size, zero if there isn't one. - private static int GetDescriptorSize(ZipUpdate update, bool includingSignature) - { - if (!((GeneralBitFlags)update.Entry.Flags).HasFlag(GeneralBitFlags.Descriptor)) - return 0; - - var descriptorWithSignature = update.Entry.LocalHeaderRequiresZip64 - ? ZipConstants.Zip64DataDescriptorSize - : ZipConstants.DataDescriptorSize; - - return includingSignature - ? descriptorWithSignature - : descriptorWithSignature - sizeof(int); - } - - private void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition) - { - var buffer = GetBuffer(); ; - - stream.Position = sourcePosition; - stream.Read(buffer, 0, sizeof(int)); - var sourceHasSignature = BitConverter.ToUInt32(buffer, 0) == ZipConstants.DataDescriptorSignature; - - var bytesToCopy = GetDescriptorSize(update, sourceHasSignature); - - while (bytesToCopy > 0) - { - stream.Position = sourcePosition; - - var bytesRead = stream.Read(buffer, 0, bytesToCopy); - if (bytesRead > 0) - { - stream.Position = destinationPosition; - stream.Write(buffer, 0, bytesRead); - bytesToCopy -= bytesRead; - destinationPosition += bytesRead; - sourcePosition += bytesRead; - } - else - { - throw new ZipException("Unexpected end of stream"); - } - } - } - - private void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition) - { - long bytesToCopy = update.Entry.CompressedSize; - - // NOTE: Compressed size is updated elsewhere. - var crc = new Crc32(); - byte[] buffer = GetBuffer(); - - long targetBytes = bytesToCopy; - long totalBytesRead = 0; - - int bytesRead; - do - { - int readSize = buffer.Length; - - if (bytesToCopy < readSize) - { - readSize = (int)bytesToCopy; - } - - stream.Position = sourcePosition; - bytesRead = stream.Read(buffer, 0, readSize); - if (bytesRead > 0) - { - if (updateCrc) - { - crc.Update(new ArraySegment(buffer, 0, bytesRead)); - } - stream.Position = destinationPosition; - stream.Write(buffer, 0, bytesRead); - - destinationPosition += bytesRead; - sourcePosition += bytesRead; - bytesToCopy -= bytesRead; - totalBytesRead += bytesRead; - } - } - while ((bytesRead > 0) && (bytesToCopy > 0)); - - if (totalBytesRead != targetBytes) - { - throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead)); - } - - if (updateCrc) - { - update.OutEntry.Crc = crc.Value; - } - } - - private int FindExistingUpdate(ZipEntry entry) - { - int result = -1; - if (updateIndex_.ContainsKey(entry.Name)) - { - result = (int)updateIndex_[entry.Name]; - } - /* - // This is slow like the coming of the next ice age but takes less storage and may be useful - // for CF? - for (int index = 0; index < updates_.Count; ++index) - { - ZipUpdate zu = ( ZipUpdate )updates_[index]; - if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && - (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { - result = index; - break; - } - } - */ - return result; - } - - private int FindExistingUpdate(string fileName, bool isEntryName = false) - { - int result = -1; - - string convertedName = !isEntryName ? GetTransformedFileName(fileName) : fileName; - - if (updateIndex_.ContainsKey(convertedName)) - { - result = (int)updateIndex_[convertedName]; - } - - /* - // This is slow like the coming of the next ice age but takes less storage and may be useful - // for CF? - for ( int index = 0; index < updates_.Count; ++index ) { - if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name, - true, CultureInfo.InvariantCulture) == 0 ) { - result = index; - break; - } - } - */ - - return result; - } - - /// - /// Get an output stream for the specified - /// - /// The entry to get an output stream for. - /// The output stream obtained for the entry. - private Stream GetOutputStream(ZipEntry entry) - { - Stream result = baseStream_; - - if (entry.IsCrypted == true) - { - result = CreateAndInitEncryptionStream(result, entry); - } - - switch (entry.CompressionMethod) - { - case CompressionMethod.Stored: - if (!entry.IsCrypted) - { - // If there is an encryption stream in use, that can be returned directly - // otherwise, wrap the base stream in an UncompressedStream instead of returning it directly - result = new UncompressedStream(result); - } - break; - - case CompressionMethod.Deflated: - var dos = new DeflaterOutputStream(result, new Deflater(9, true)) - { - // If there is an encryption stream in use, then we want that to be disposed when the deflator stream is disposed - // If not, then we don't want it to dispose the base stream - IsStreamOwner = entry.IsCrypted - }; - result = dos; - break; - - case CompressionMethod.BZip2: - var bzos = new BZip2.BZip2OutputStream(result) - { - // If there is an encryption stream in use, then we want that to be disposed when the BZip2OutputStream stream is disposed - // If not, then we don't want it to dispose the base stream - IsStreamOwner = entry.IsCrypted - }; - result = bzos; - break; - - default: - throw new ZipException("Unknown compression method " + entry.CompressionMethod); - } - return result; - } - - private void AddEntry(ZipFile workFile, ZipUpdate update) - { - Stream source = null; - - if (update.Entry.IsFile) - { - source = update.GetSource(); - - if (source == null) - { - source = updateDataSource_.GetSource(update.Entry, update.Filename); - } - } - - var useCrc = update.Entry.AESKeySize == 0; - - if (source != null) - { - using (source) - { - long sourceStreamLength = source.Length; - if (update.OutEntry.Size < 0) - { - update.OutEntry.Size = sourceStreamLength; - } - else - { - // Check for errant entries. - if (update.OutEntry.Size != sourceStreamLength) - { - throw new ZipException("Entry size/stream size mismatch"); - } - } - - workFile.WriteLocalEntryHeader(update); - - long dataStart = workFile.baseStream_.Position; - - using (Stream output = workFile.GetOutputStream(update.OutEntry)) - { - CopyBytes(update, output, source, sourceStreamLength, useCrc); - } - - long dataEnd = workFile.baseStream_.Position; - update.OutEntry.CompressedSize = dataEnd - dataStart; - - if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) - { - var helper = new ZipHelperStream(workFile.baseStream_); - helper.WriteDataDescriptor(update.OutEntry); - } - } - } - else - { - workFile.WriteLocalEntryHeader(update); - update.OutEntry.CompressedSize = 0; - } - } - - private void ModifyEntry(ZipFile workFile, ZipUpdate update) - { - workFile.WriteLocalEntryHeader(update); - long dataStart = workFile.baseStream_.Position; - - // TODO: This is slow if the changes don't effect the data!! - if (update.Entry.IsFile && (update.Filename != null)) - { - using (Stream output = workFile.GetOutputStream(update.OutEntry)) - { - using (Stream source = this.GetInputStream(update.Entry)) - { - CopyBytes(update, output, source, source.Length, true); - } - } - } - - long dataEnd = workFile.baseStream_.Position; - update.Entry.CompressedSize = dataEnd - dataStart; - } - - private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition) - { - bool skipOver = false || update.Entry.Offset == destinationPosition; - - if (!skipOver) - { - baseStream_.Position = destinationPosition; - workFile.WriteLocalEntryHeader(update); - destinationPosition = baseStream_.Position; - } - - long sourcePosition = 0; - - const int NameLengthOffset = 26; - - // TODO: Add base for SFX friendly handling - long entryDataOffset = update.Entry.Offset + NameLengthOffset; - - baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); - - // Clumsy way of handling retrieving the original name and extra data length for now. - // TODO: Stop re-reading name and data length in CopyEntryDirect. - - uint nameLength = ReadLEUshort(); - uint extraLength = ReadLEUshort(); - - sourcePosition = baseStream_.Position + nameLength + extraLength; - - if (skipOver) - { - if (update.OffsetBasedSize != -1) - { - destinationPosition += update.OffsetBasedSize; - } - else - { - // Skip entry header - destinationPosition += (sourcePosition - entryDataOffset) + NameLengthOffset; - - // Skip entry compressed data - destinationPosition += update.Entry.CompressedSize; - - // Seek to end of entry to check for descriptor signature - baseStream_.Seek(destinationPosition, SeekOrigin.Begin); - - var descriptorHasSignature = ReadLEUint() == ZipConstants.DataDescriptorSignature; - - // Skip descriptor and it's signature (if present) - destinationPosition += GetDescriptorSize(update, descriptorHasSignature); - } - } - else - { - if (update.Entry.CompressedSize > 0) - { - CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition); - } - CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition); - } - } - - private void CopyEntry(ZipFile workFile, ZipUpdate update) - { - workFile.WriteLocalEntryHeader(update); - - if (update.Entry.CompressedSize > 0) - { - const int NameLengthOffset = 26; - - long entryDataOffset = update.Entry.Offset + NameLengthOffset; - - // TODO: This wont work for SFX files! - baseStream_.Seek(entryDataOffset, SeekOrigin.Begin); - - uint nameLength = ReadLEUshort(); - uint extraLength = ReadLEUshort(); - - baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current); - - CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false); - } - CopyDescriptorBytes(update, workFile.baseStream_, baseStream_); - } - - private void Reopen(Stream source) - { - isNewArchive_ = false; - baseStream_ = source ?? throw new ZipException("Failed to reopen archive - no source"); - ReadEntries(); - } - - private void Reopen() - { - if (Name == null) - { - throw new InvalidOperationException("Name is not known cannot Reopen"); - } - - Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read)); - } - - private void UpdateCommentOnly() - { - long baseLength = baseStream_.Length; - - ZipHelperStream updateFile = null; - - if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) - { - Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_); - updateFile = new ZipHelperStream(copyStream) - { - IsStreamOwner = true - }; - - baseStream_.Dispose(); - baseStream_ = null; - } - else - { - if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) - { - // TODO: archiveStorage wasnt originally intended for this use. - // Need to revisit this to tidy up handling as archive storage currently doesnt - // handle the original stream well. - // The problem is when using an existing zip archive with an in memory archive storage. - // The open stream wont support writing but the memory storage should open the same file not an in memory one. - - // Need to tidy up the archive storage interface and contract basically. - baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_); - updateFile = new ZipHelperStream(baseStream_); - } - else - { - baseStream_.Dispose(); - baseStream_ = null; - updateFile = new ZipHelperStream(Name); - } - } - - using (updateFile) - { - long locatedCentralDirOffset = - updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, - baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); - if (locatedCentralDirOffset < 0) - { - throw new ZipException("Cannot find central directory"); - } - - const int CentralHeaderCommentSizeOffset = 16; - updateFile.Position += CentralHeaderCommentSizeOffset; - - byte[] rawComment = newComment_.RawComment; - - updateFile.WriteLEShort(rawComment.Length); - updateFile.Write(rawComment, 0, rawComment.Length); - updateFile.SetLength(updateFile.Position); - } - - if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) - { - Reopen(archiveStorage_.ConvertTemporaryToFinal()); - } - else - { - ReadEntries(); - } - } - - /// - /// Class used to sort updates. - /// - private class UpdateComparer : IComparer - { - /// - /// Compares two objects and returns a value indicating whether one is - /// less than, equal to or greater than the other. - /// - /// First object to compare - /// Second object to compare. - /// Compare result. - public int Compare(ZipUpdate x, ZipUpdate y) - { - int result; - - if (x == null) - { - if (y == null) - { - result = 0; - } - else - { - result = -1; - } - } - else if (y == null) - { - result = 1; - } - else - { - int xCmdValue = ((x.Command == UpdateCommand.Copy) || (x.Command == UpdateCommand.Modify)) ? 0 : 1; - int yCmdValue = ((y.Command == UpdateCommand.Copy) || (y.Command == UpdateCommand.Modify)) ? 0 : 1; - - result = xCmdValue - yCmdValue; - if (result == 0) - { - long offsetDiff = x.Entry.Offset - y.Entry.Offset; - if (offsetDiff < 0) - { - result = -1; - } - else if (offsetDiff == 0) - { - result = 0; - } - else - { - result = 1; - } - } - } - return result; - } - } - - private void RunUpdates() - { - long sizeEntries = 0; - long endOfStream = 0; - bool directUpdate = false; - long destinationPosition = 0; // NOT SFX friendly - - ZipFile workFile; - - if (IsNewArchive) - { - workFile = this; - workFile.baseStream_.Position = 0; - directUpdate = true; - } - else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) - { - workFile = this; - workFile.baseStream_.Position = 0; - directUpdate = true; - - // Sort the updates by offset within copies/modifies, then adds. - // This ensures that data required by copies will not be overwritten. - updates_.Sort(new UpdateComparer()); - } - else - { - workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput()); - workFile.UseZip64 = UseZip64; - - if (key != null) - { - workFile.key = (byte[])key.Clone(); - } - } - - try - { - foreach (ZipUpdate update in updates_) - { - if (update != null) - { - switch (update.Command) - { - case UpdateCommand.Copy: - if (directUpdate) - { - CopyEntryDirect(workFile, update, ref destinationPosition); - } - else - { - CopyEntry(workFile, update); - } - break; - - case UpdateCommand.Modify: - // TODO: Direct modifying of an entry will take some legwork. - ModifyEntry(workFile, update); - break; - - case UpdateCommand.Add: - if (!IsNewArchive && directUpdate) - { - workFile.baseStream_.Position = destinationPosition; - } - - AddEntry(workFile, update); - - if (directUpdate) - { - destinationPosition = workFile.baseStream_.Position; - } - break; - } - } - } - - if (!IsNewArchive && directUpdate) - { - workFile.baseStream_.Position = destinationPosition; - } - - long centralDirOffset = workFile.baseStream_.Position; - - foreach (ZipUpdate update in updates_) - { - if (update != null) - { - sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry); - } - } - - byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipStrings.ConvertToArray(comment_); - using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) - { - zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment); - } - - endOfStream = workFile.baseStream_.Position; - - // And now patch entries... - foreach (ZipUpdate update in updates_) - { - if (update != null) - { - // If the size of the entry is zero leave the crc as 0 as well. - // The calculated crc will be all bits on... - if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) - { - workFile.baseStream_.Position = update.CrcPatchOffset; - workFile.WriteLEInt((int)update.OutEntry.Crc); - } - - if (update.SizePatchOffset > 0) - { - workFile.baseStream_.Position = update.SizePatchOffset; - if (update.OutEntry.LocalHeaderRequiresZip64) - { - workFile.WriteLeLong(update.OutEntry.Size); - workFile.WriteLeLong(update.OutEntry.CompressedSize); - } - else - { - workFile.WriteLEInt((int)update.OutEntry.CompressedSize); - workFile.WriteLEInt((int)update.OutEntry.Size); - } - } - } - } - } - catch - { - workFile.Close(); - if (!directUpdate && (workFile.Name != null)) - { - File.Delete(workFile.Name); - } - throw; - } - - if (directUpdate) - { - workFile.baseStream_.SetLength(endOfStream); - workFile.baseStream_.Flush(); - isNewArchive_ = false; - ReadEntries(); - } - else - { - baseStream_.Dispose(); - Reopen(archiveStorage_.ConvertTemporaryToFinal()); - } - } - - private void CheckUpdating() - { - if (updates_ == null) - { - throw new InvalidOperationException("BeginUpdate has not been called"); - } - } - - #endregion Update Support - - #region ZipUpdate class - - /// - /// Represents a pending update to a Zip file. - /// - private class ZipUpdate - { - #region Constructors - - public ZipUpdate(string fileName, ZipEntry entry) - { - command_ = UpdateCommand.Add; - entry_ = entry; - filename_ = fileName; - } - - [Obsolete] - public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) - { - command_ = UpdateCommand.Add; - entry_ = new ZipEntry(entryName) - { - CompressionMethod = compressionMethod - }; - filename_ = fileName; - } - - [Obsolete] - public ZipUpdate(string fileName, string entryName) - : this(fileName, entryName, CompressionMethod.Deflated) - { - // Do nothing. - } - - [Obsolete] - public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) - { - command_ = UpdateCommand.Add; - entry_ = new ZipEntry(entryName) - { - CompressionMethod = compressionMethod - }; - dataSource_ = dataSource; - } - - public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) - { - command_ = UpdateCommand.Add; - entry_ = entry; - dataSource_ = dataSource; - } - - public ZipUpdate(ZipEntry original, ZipEntry updated) - { - throw new ZipException("Modify not currently supported"); - /* - command_ = UpdateCommand.Modify; - entry_ = ( ZipEntry )original.Clone(); - outEntry_ = ( ZipEntry )updated.Clone(); - */ - } - - public ZipUpdate(UpdateCommand command, ZipEntry entry) - { - command_ = command; - entry_ = (ZipEntry)entry.Clone(); - } - - /// - /// Copy an existing entry. - /// - /// The existing entry to copy. - public ZipUpdate(ZipEntry entry) - : this(UpdateCommand.Copy, entry) - { - // Do nothing. - } - - #endregion Constructors - - /// - /// Get the for this update. - /// - /// This is the source or original entry. - public ZipEntry Entry - { - get { return entry_; } - } - - /// - /// Get the that will be written to the updated/new file. - /// - public ZipEntry OutEntry - { - get - { - if (outEntry_ == null) - { - outEntry_ = (ZipEntry)entry_.Clone(); - } - - return outEntry_; - } - } - - /// - /// Get the command for this update. - /// - public UpdateCommand Command - { - get { return command_; } - } - - /// - /// Get the filename if any for this update. Null if none exists. - /// - public string Filename - { - get { return filename_; } - } - - /// - /// Get/set the location of the size patch for this update. - /// - public long SizePatchOffset - { - get { return sizePatchOffset_; } - set { sizePatchOffset_ = value; } - } - - /// - /// Get /set the location of the crc patch for this update. - /// - public long CrcPatchOffset - { - get { return crcPatchOffset_; } - set { crcPatchOffset_ = value; } - } - - /// - /// Get/set the size calculated by offset. - /// Specifically, the difference between this and next entry's starting offset. - /// - public long OffsetBasedSize - { - get { return _offsetBasedSize; } - set { _offsetBasedSize = value; } - } - - public Stream GetSource() - { - Stream result = null; - if (dataSource_ != null) - { - result = dataSource_.GetSource(); - } - - return result; - } - - #region Instance Fields - - private ZipEntry entry_; - private ZipEntry outEntry_; - private readonly UpdateCommand command_; - private IStaticDataSource dataSource_; - private readonly string filename_; - private long sizePatchOffset_ = -1; - private long crcPatchOffset_ = -1; - private long _offsetBasedSize = -1; - - #endregion Instance Fields - } - - #endregion ZipUpdate class - - #endregion Updating - - #region Disposing - - #region IDisposable Members - - void IDisposable.Dispose() - { - Close(); - } - - #endregion IDisposable Members - - private void DisposeInternal(bool disposing) - { - if (!isDisposed_) - { - isDisposed_ = true; - entries_ = Empty.Array(); - - if (IsStreamOwner && (baseStream_ != null)) - { - lock (baseStream_) - { - baseStream_.Dispose(); - } - } - - PostUpdateCleanup(); - } - } - - /// - /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; - /// false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - DisposeInternal(disposing); - } - - #endregion Disposing - - #region Internal routines - - #region Reading - - /// - /// Read an unsigned short in little endian byte order. - /// - /// Returns the value read. - /// - /// The stream ends prematurely - /// - private ushort ReadLEUshort() - { - int data1 = baseStream_.ReadByte(); - - if (data1 < 0) - { - throw new EndOfStreamException("End of stream"); - } - - int data2 = baseStream_.ReadByte(); - - if (data2 < 0) - { - throw new EndOfStreamException("End of stream"); - } - - return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8))); - } - - /// - /// Read a uint in little endian byte order. - /// - /// Returns the value read. - /// - /// An i/o error occurs. - /// - /// - /// The file ends prematurely - /// - private uint ReadLEUint() - { - return (uint)(ReadLEUshort() | (ReadLEUshort() << 16)); - } - - private ulong ReadLEUlong() - { - return ReadLEUint() | ((ulong)ReadLEUint() << 32); - } - - #endregion Reading - - // NOTE this returns the offset of the first byte after the signature. - private long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) - { - using (ZipHelperStream les = new ZipHelperStream(baseStream_)) - { - return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData); - } - } - - /// - /// Search for and read the central directory of a zip file filling the entries array. - /// - /// - /// An i/o error occurs. - /// - /// - /// The central directory is malformed or cannot be found - /// - private void ReadEntries() - { - // Search for the End Of Central Directory. When a zip comment is - // present the directory will start earlier - // - // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. - // This should be compatible with both SFX and ZIP files but has only been tested for Zip files - // If a SFX file has the Zip data attached as a resource and there are other resources occurring later then - // this could be invalid. - // Could also speed this up by reading memory in larger blocks. - - if (baseStream_.CanSeek == false) - { - throw new ZipException("ZipFile stream must be seekable"); - } - - long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, - baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); - - if (locatedEndOfCentralDir < 0) - { - throw new ZipException("Cannot find central directory"); - } - - // Read end of central directory record - ushort thisDiskNumber = ReadLEUshort(); - ushort startCentralDirDisk = ReadLEUshort(); - ulong entriesForThisDisk = ReadLEUshort(); - ulong entriesForWholeCentralDir = ReadLEUshort(); - ulong centralDirSize = ReadLEUint(); - long offsetOfCentralDir = ReadLEUint(); - uint commentSize = ReadLEUshort(); - - if (commentSize > 0) - { - byte[] comment = new byte[commentSize]; - - StreamUtils.ReadFully(baseStream_, comment); - comment_ = ZipStrings.ConvertToString(comment); - } - else - { - comment_ = string.Empty; - } - - bool isZip64 = false; - bool requireZip64 = false; - - // Check if zip64 header information is required. - if ((thisDiskNumber == 0xffff) || - (startCentralDirDisk == 0xffff) || - (entriesForThisDisk == 0xffff) || - (entriesForWholeCentralDir == 0xffff) || - (centralDirSize == 0xffffffff) || - (offsetOfCentralDir == 0xffffffff)) - { - requireZip64 = true; - } - - // #357 - always check for the existance of the Zip64 central directory. - // #403 - Take account of the fixed size of the locator when searching. - // Subtract from locatedEndOfCentralDir so that the endLocation is the location of EndOfCentralDirectorySignature, - // rather than the data following the signature. - long locatedZip64EndOfCentralDirLocator = LocateBlockWithSignature( - ZipConstants.Zip64CentralDirLocatorSignature, - locatedEndOfCentralDir - 4, - ZipConstants.Zip64EndOfCentralDirectoryLocatorSize, - 0); - - if (locatedZip64EndOfCentralDirLocator < 0) - { - if (requireZip64) - { - // This is only an error in cases where the Zip64 directory is required. - throw new ZipException("Cannot find Zip64 locator"); - } - } - else - { - isZip64 = true; - - // number of the disk with the start of the zip64 end of central directory 4 bytes - // relative offset of the zip64 end of central directory record 8 bytes - // total number of disks 4 bytes - ReadLEUint(); // startDisk64 is not currently used - ulong offset64 = ReadLEUlong(); - uint totalDisks = ReadLEUint(); - - baseStream_.Position = (long)offset64; - long sig64 = ReadLEUint(); - - if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) - { - throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); - } - - // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. - ulong recordSize = ReadLEUlong(); - int versionMadeBy = ReadLEUshort(); - int versionToExtract = ReadLEUshort(); - uint thisDisk = ReadLEUint(); - uint centralDirDisk = ReadLEUint(); - entriesForThisDisk = ReadLEUlong(); - entriesForWholeCentralDir = ReadLEUlong(); - centralDirSize = ReadLEUlong(); - offsetOfCentralDir = (long)ReadLEUlong(); - - // NOTE: zip64 extensible data sector (variable size) is ignored. - } - - entries_ = new ZipEntry[entriesForThisDisk]; - - // SFX/embedded support, find the offset of the first entry vis the start of the stream - // This applies to Zip files that are appended to the end of an SFX stub. - // Or are appended as a resource to an executable. - // Zip files created by some archivers have the offsets altered to reflect the true offsets - // and so dont require any adjustment here... - // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? - if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) - { - offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir); - if (offsetOfFirstEntry <= 0) - { - throw new ZipException("Invalid embedded zip archive"); - } - } - - baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); - - for (ulong i = 0; i < entriesForThisDisk; i++) - { - if (ReadLEUint() != ZipConstants.CentralHeaderSignature) - { - throw new ZipException("Wrong Central Directory signature"); - } - - int versionMadeBy = ReadLEUshort(); - int versionToExtract = ReadLEUshort(); - int bitFlags = ReadLEUshort(); - int method = ReadLEUshort(); - uint dostime = ReadLEUint(); - uint crc = ReadLEUint(); - var csize = (long)ReadLEUint(); - var size = (long)ReadLEUint(); - int nameLen = ReadLEUshort(); - int extraLen = ReadLEUshort(); - int commentLen = ReadLEUshort(); - - int diskStartNo = ReadLEUshort(); // Not currently used - int internalAttributes = ReadLEUshort(); // Not currently used - - uint externalAttributes = ReadLEUint(); - long offset = ReadLEUint(); - - byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; - - StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); - string name = ZipStrings.ConvertToStringExt(bitFlags, buffer, nameLen); - - var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method) - { - Crc = crc & 0xffffffffL, - Size = size & 0xffffffffL, - CompressedSize = csize & 0xffffffffL, - Flags = bitFlags, - DosTime = dostime, - ZipFileIndex = (long)i, - Offset = offset, - ExternalFileAttributes = (int)externalAttributes - }; - - if ((bitFlags & 8) == 0) - { - entry.CryptoCheckValue = (byte)(crc >> 24); - } - else - { - entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); - } - - if (extraLen > 0) - { - byte[] extra = new byte[extraLen]; - StreamUtils.ReadFully(baseStream_, extra); - entry.ExtraData = extra; - } - - entry.ProcessExtraData(false); - - if (commentLen > 0) - { - StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); - entry.Comment = ZipStrings.ConvertToStringExt(bitFlags, buffer, commentLen); - } - - entries_[i] = entry; - } - } - - /// - /// Locate the data for a given entry. - /// - /// - /// The start offset of the data. - /// - /// - /// The stream ends prematurely - /// - /// - /// The local header signature is invalid, the entry and central header file name lengths are different - /// or the local and entry compression methods dont match - /// - private long LocateEntry(ZipEntry entry) - { - return TestLocalHeader(entry, HeaderTest.Extract); - } - - private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) - { - CryptoStream result = null; - - if (entry.CompressionMethodForHeader == CompressionMethod.WinZipAES) - { - if (entry.Version >= ZipConstants.VERSION_AES) - { - // Issue #471 - accept an empty string as a password, but reject null. - OnKeysRequired(entry.Name); - if (rawPassword_ == null) - { - throw new ZipException("No password available for AES encrypted stream"); - } - int saltLen = entry.AESSaltLen; - byte[] saltBytes = new byte[saltLen]; - int saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, 0, saltLen); - if (saltIn != saltLen) - throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn); - // - byte[] pwdVerifyRead = new byte[2]; - StreamUtils.ReadFully(baseStream, pwdVerifyRead); - int blockSize = entry.AESKeySize / 8; // bits to bytes - - var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false); - byte[] pwdVerifyCalc = decryptor.PwdVerifier; - if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1]) - throw new ZipException("Invalid password for AES"); - result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read); - } - else - { - throw new ZipException("Decryption method not supported"); - } - } - else - { - if ((entry.Version < ZipConstants.VersionStrongEncryption) - || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) - { - var classicManaged = new PkzipClassicManaged(); - - OnKeysRequired(entry.Name); - if (HaveKeys == false) - { - throw new ZipException("No password available for encrypted stream"); - } - - result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); - CheckClassicPassword(result, entry); - } - else - { - // We don't support PKWare strong encryption - throw new ZipException("Decryption method not supported"); - } - } - - return result; - } - - private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) - { - CryptoStream result = null; - if ((entry.Version < ZipConstants.VersionStrongEncryption) - || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) - { - var classicManaged = new PkzipClassicManaged(); - - OnKeysRequired(entry.Name); - if (HaveKeys == false) - { - throw new ZipException("No password available for encrypted stream"); - } - - // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream - // which doesnt do this. - result = new CryptoStream(new UncompressedStream(baseStream), - classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); - - if ((entry.Crc < 0) || (entry.Flags & 8) != 0) - { - WriteEncryptionHeader(result, entry.DosTime << 16); - } - else - { - WriteEncryptionHeader(result, entry.Crc); - } - } - return result; - } - - private static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) - { - byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; - StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); - if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) - throw new ZipException("Invalid password"); - } - - private static void WriteEncryptionHeader(Stream stream, long crcValue) - { - byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; - var rng = RandomNumberGenerator.Create(); - rng.GetBytes(cryptBuffer); - cryptBuffer[11] = (byte)(crcValue >> 24); - stream.Write(cryptBuffer, 0, cryptBuffer.Length); - } - - #endregion Internal routines - - #region Instance Fields - - private bool isDisposed_; - private string name_; - private string comment_; - private string rawPassword_; - private Stream baseStream_; - private bool isStreamOwner; - private long offsetOfFirstEntry; - private ZipEntry[] entries_; - private byte[] key; - private bool isNewArchive_; - - // Default is dynamic which is not backwards compatible and can cause problems - // with XP's built in compression which cant read Zip64 archives. - // However it does avoid the situation were a large file is added and cannot be completed correctly. - // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed. - private UseZip64 useZip64_ = UseZip64.Dynamic; - - #region Zip Update Instance Fields - - private List updates_; - private long updateCount_; // Count is managed manually as updates_ can contain nulls! - private Dictionary updateIndex_; - private IArchiveStorage archiveStorage_; - private IDynamicDataSource updateDataSource_; - private bool contentsEdited_; - private int bufferSize_ = DefaultBufferSize; - private byte[] copyBuffer_; - private ZipString newComment_; - private bool commentEdited_; - private IEntryFactory updateEntryFactory_ = new ZipEntryFactory(); - - #endregion Zip Update Instance Fields - - #endregion Instance Fields - - #region Support Classes - - /// - /// Represents a string from a which is stored as an array of bytes. - /// - private class ZipString - { - #region Constructors - - /// - /// Initialise a with a string. - /// - /// The textual string form. - public ZipString(string comment) - { - comment_ = comment; - isSourceString_ = true; - } - - /// - /// Initialise a using a string in its binary 'raw' form. - /// - /// - public ZipString(byte[] rawString) - { - rawComment_ = rawString; - } - - #endregion Constructors - - /// - /// Get a value indicating the original source of data for this instance. - /// True if the source was a string; false if the source was binary data. - /// - public bool IsSourceString - { - get { return isSourceString_; } - } - - /// - /// Get the length of the comment when represented as raw bytes. - /// - public int RawLength - { - get - { - MakeBytesAvailable(); - return rawComment_.Length; - } - } - - /// - /// Get the comment in its 'raw' form as plain bytes. - /// - public byte[] RawComment - { - get - { - MakeBytesAvailable(); - return (byte[])rawComment_.Clone(); - } - } - - /// - /// Reset the comment to its initial state. - /// - public void Reset() - { - if (isSourceString_) - { - rawComment_ = null; - } - else - { - comment_ = null; - } - } - - private void MakeTextAvailable() - { - if (comment_ == null) - { - comment_ = ZipStrings.ConvertToString(rawComment_); - } - } - - private void MakeBytesAvailable() - { - if (rawComment_ == null) - { - rawComment_ = ZipStrings.ConvertToArray(comment_); - } - } - - /// - /// Implicit conversion of comment to a string. - /// - /// The to convert to a string. - /// The textual equivalent for the input value. - static public implicit operator string(ZipString zipString) - { - zipString.MakeTextAvailable(); - return zipString.comment_; - } - - #region Instance Fields - - private string comment_; - private byte[] rawComment_; - private readonly bool isSourceString_; - - #endregion Instance Fields - } - - /// - /// An enumerator for Zip entries - /// - private class ZipEntryEnumerator : IEnumerator - { - #region Constructors - - public ZipEntryEnumerator(ZipEntry[] entries) - { - array = entries; - } - - #endregion Constructors - - #region IEnumerator Members - - public object Current - { - get - { - return array[index]; - } - } - - public void Reset() - { - index = -1; - } - - public bool MoveNext() - { - return (++index < array.Length); - } - - #endregion IEnumerator Members - - #region Instance Fields - - private ZipEntry[] array; - private int index = -1; - - #endregion Instance Fields - } - - /// - /// An is a stream that you can write uncompressed data - /// to and flush, but cannot read, seek or do anything else to. - /// - private class UncompressedStream : Stream - { - #region Constructors - - public UncompressedStream(Stream baseStream) - { - baseStream_ = baseStream; - } - - #endregion Constructors - - /// - /// Gets a value indicating whether the current stream supports reading. - /// - public override bool CanRead - { - get - { - return false; - } - } - - /// - /// Write any buffered data to underlying storage. - /// - public override void Flush() - { - baseStream_.Flush(); - } - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - public override bool CanWrite - { - get - { - return baseStream_.CanWrite; - } - } - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - public override bool CanSeek - { - get - { - return false; - } - } - - /// - /// Get the length in bytes of the stream. - /// - public override long Length - { - get - { - return 0; - } - } - - /// - /// Gets or sets the position within the current stream. - /// - public override long Position - { - get - { - return baseStream_.Position; - } - set - { - throw new NotImplementedException(); - } - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// - /// The sum of offset and count is larger than the buffer length. - /// Methods were called after the stream was closed. - /// The stream does not support reading. - /// buffer is null. - /// An I/O error occurs. - /// offset or count is negative. - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } - - /// - /// Sets the position within the current stream. - /// - /// A byte offset relative to the origin parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// - /// The new position within the current stream. - /// - /// An I/O error occurs. - /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. - /// Methods were called after the stream was closed. - public override long Seek(long offset, SeekOrigin origin) - { - return 0; - } - - /// - /// Sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. - /// An I/O error occurs. - /// Methods were called after the stream was closed. - public override void SetLength(long value) - { - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies count bytes from buffer to the current stream. - /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - /// An I/O error occurs. - /// The stream does not support writing. - /// Methods were called after the stream was closed. - /// buffer is null. - /// The sum of offset and count is greater than the buffer length. - /// offset or count is negative. - public override void Write(byte[] buffer, int offset, int count) - { - baseStream_.Write(buffer, offset, count); - } - - private readonly - - #region Instance Fields - - Stream baseStream_; - - #endregion Instance Fields - } - - /// - /// A is an - /// whose data is only a part or subsection of a file. - /// - private class PartialInputStream : Stream - { - #region Constructors - - /// - /// Initialise a new instance of the class. - /// - /// The containing the underlying stream to use for IO. - /// The start of the partial data. - /// The length of the partial data. - public PartialInputStream(ZipFile zipFile, long start, long length) - { - start_ = start; - length_ = length; - - // Although this is the only time the zipfile is used - // keeping a reference here prevents premature closure of - // this zip file and thus the baseStream_. - - // Code like this will cause apparently random failures depending - // on the size of the files and when garbage is collected. - // - // ZipFile z = new ZipFile (stream); - // Stream reader = z.GetInputStream(0); - // uses reader here.... - zipFile_ = zipFile; - baseStream_ = zipFile_.baseStream_; - readPos_ = start; - end_ = start + length; - } - - #endregion Constructors - - /// - /// Read a byte from this stream. - /// - /// Returns the byte read or -1 on end of stream. - public override int ReadByte() - { - if (readPos_ >= end_) - { - // -1 is the correct value at end of stream. - return -1; - } - - lock (baseStream_) - { - baseStream_.Seek(readPos_++, SeekOrigin.Begin); - return baseStream_.ReadByte(); - } - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// - /// The sum of offset and count is larger than the buffer length. - /// Methods were called after the stream was closed. - /// The stream does not support reading. - /// buffer is null. - /// An I/O error occurs. - /// offset or count is negative. - public override int Read(byte[] buffer, int offset, int count) - { - lock (baseStream_) - { - if (count > end_ - readPos_) - { - count = (int)(end_ - readPos_); - if (count == 0) - { - return 0; - } - } - // Protect against Stream implementations that throw away their buffer on every Seek - // (for example, Mono FileStream) - if (baseStream_.Position != readPos_) - { - baseStream_.Seek(readPos_, SeekOrigin.Begin); - } - int readCount = baseStream_.Read(buffer, offset, count); - if (readCount > 0) - { - readPos_ += readCount; - } - return readCount; - } - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies count bytes from buffer to the current stream. - /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - /// An I/O error occurs. - /// The stream does not support writing. - /// Methods were called after the stream was closed. - /// buffer is null. - /// The sum of offset and count is greater than the buffer length. - /// offset or count is negative. - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - /// - /// When overridden in a derived class, sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. - /// An I/O error occurs. - /// Methods were called after the stream was closed. - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - /// When overridden in a derived class, sets the position within the current stream. - /// - /// A byte offset relative to the origin parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// - /// The new position within the current stream. - /// - /// An I/O error occurs. - /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. - /// Methods were called after the stream was closed. - public override long Seek(long offset, SeekOrigin origin) - { - long newPos = readPos_; - - switch (origin) - { - case SeekOrigin.Begin: - newPos = start_ + offset; - break; - - case SeekOrigin.Current: - newPos = readPos_ + offset; - break; - - case SeekOrigin.End: - newPos = end_ + offset; - break; - } - - if (newPos < start_) - { - throw new ArgumentException("Negative position is invalid"); - } - - if (newPos > end_) - { - throw new IOException("Cannot seek past end"); - } - readPos_ = newPos; - return readPos_; - } - - /// - /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. - /// - /// An I/O error occurs. - public override void Flush() - { - // Nothing to do. - } - - /// - /// Gets or sets the position within the current stream. - /// - /// - /// The current position within the stream. - /// An I/O error occurs. - /// The stream does not support seeking. - /// Methods were called after the stream was closed. - public override long Position - { - get { return readPos_ - start_; } - set - { - long newPos = start_ + value; - - if (newPos < start_) - { - throw new ArgumentException("Negative position is invalid"); - } - - if (newPos > end_) - { - throw new InvalidOperationException("Cannot seek past end"); - } - readPos_ = newPos; - } - } - - /// - /// Gets the length in bytes of the stream. - /// - /// - /// A long value representing the length of the stream in bytes. - /// A class derived from Stream does not support seeking. - /// Methods were called after the stream was closed. - public override long Length - { - get { return length_; } - } - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - /// false - /// true if the stream supports writing; otherwise, false. - public override bool CanWrite - { - get { return false; } - } - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - /// true - /// true if the stream supports seeking; otherwise, false. - public override bool CanSeek - { - get { return true; } - } - - /// - /// Gets a value indicating whether the current stream supports reading. - /// - /// true. - /// true if the stream supports reading; otherwise, false. - public override bool CanRead - { - get { return true; } - } - - /// - /// Gets a value that determines whether the current stream can time out. - /// - /// - /// A value that determines whether the current stream can time out. - public override bool CanTimeout - { - get { return baseStream_.CanTimeout; } - } - - #region Instance Fields - - private ZipFile zipFile_; - private Stream baseStream_; - private readonly long start_; - private readonly long length_; - private long readPos_; - private readonly long end_; - - #endregion Instance Fields - } - - #endregion Support Classes - } - - #endregion ZipFile Class - - #region DataSources - - /// - /// Provides a static way to obtain a source of data for an entry. - /// - public interface IStaticDataSource - { - /// - /// Get a source of data by creating a new stream. - /// - /// Returns a to use for compression input. - /// Ideally a new stream is created and opened to achieve this, to avoid locking problems. - Stream GetSource(); - } - - /// - /// Represents a source of data that can dynamically provide - /// multiple data sources based on the parameters passed. - /// - public interface IDynamicDataSource - { - /// - /// Get a data source. - /// - /// The to get a source for. - /// The name for data if known. - /// Returns a to use for compression input. - /// Ideally a new stream is created and opened to achieve this, to avoid locking problems. - Stream GetSource(ZipEntry entry, string name); - } - - /// - /// Default implementation of a for use with files stored on disk. - /// - public class StaticDiskDataSource : IStaticDataSource - { - /// - /// Initialise a new instance of - /// - /// The name of the file to obtain data from. - public StaticDiskDataSource(string fileName) - { - fileName_ = fileName; - } - - #region IDataSource Members - - /// - /// Get a providing data. - /// - /// Returns a providing data. - public Stream GetSource() - { - return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); - } - - private readonly - - #endregion IDataSource Members - - #region Instance Fields - - string fileName_; - - #endregion Instance Fields - } - - /// - /// Default implementation of for files stored on disk. - /// - public class DynamicDiskDataSource : IDynamicDataSource - { - #region IDataSource Members - - /// - /// Get a providing data for an entry. - /// - /// The entry to provide data for. - /// The file name for data if known. - /// Returns a stream providing data; or null if not available - public Stream GetSource(ZipEntry entry, string name) - { - Stream result = null; - - if (name != null) - { - result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); - } - - return result; - } - - #endregion IDataSource Members - } - - #endregion DataSources - - #region Archive Storage - - /// - /// Defines facilities for data storage when updating Zip Archives. - /// - public interface IArchiveStorage - { - /// - /// Get the to apply during updates. - /// - FileUpdateMode UpdateMode { get; } - - /// - /// Get an empty that can be used for temporary output. - /// - /// Returns a temporary output - /// - Stream GetTemporaryOutput(); - - /// - /// Convert a temporary output stream to a final stream. - /// - /// The resulting final - /// - Stream ConvertTemporaryToFinal(); - - /// - /// Make a temporary copy of the original stream. - /// - /// The to copy. - /// Returns a temporary output that is a copy of the input. - Stream MakeTemporaryCopy(Stream stream); - - /// - /// Return a stream suitable for performing direct updates on the original source. - /// - /// The current stream. - /// Returns a stream suitable for direct updating. - /// This may be the current stream passed. - Stream OpenForDirectUpdate(Stream stream); - - /// - /// Dispose of this instance. - /// - void Dispose(); - } - - /// - /// An abstract suitable for extension by inheritance. - /// - abstract public class BaseArchiveStorage : IArchiveStorage - { - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The update mode. - protected BaseArchiveStorage(FileUpdateMode updateMode) - { - updateMode_ = updateMode; - } - - #endregion Constructors - - #region IArchiveStorage Members - - /// - /// Gets a temporary output - /// - /// Returns the temporary output stream. - /// - public abstract Stream GetTemporaryOutput(); - - /// - /// Converts the temporary to its final form. - /// - /// Returns a that can be used to read - /// the final storage for the archive. - /// - public abstract Stream ConvertTemporaryToFinal(); - - /// - /// Make a temporary copy of a . - /// - /// The to make a copy of. - /// Returns a temporary output that is a copy of the input. - public abstract Stream MakeTemporaryCopy(Stream stream); - - /// - /// Return a stream suitable for performing direct updates on the original source. - /// - /// The to open for direct update. - /// Returns a stream suitable for direct updating. - public abstract Stream OpenForDirectUpdate(Stream stream); - - /// - /// Disposes this instance. - /// - public abstract void Dispose(); - - /// - /// Gets the update mode applicable. - /// - /// The update mode. - public FileUpdateMode UpdateMode - { - get - { - return updateMode_; - } - } - - #endregion IArchiveStorage Members - - #region Instance Fields - - private readonly FileUpdateMode updateMode_; - - #endregion Instance Fields - } - - /// - /// An implementation suitable for hard disks. - /// - public class DiskArchiveStorage : BaseArchiveStorage - { - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The file. - /// The update mode. - public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) - : base(updateMode) - { - if (file.Name == null) - { - throw new ZipException("Cant handle non file archives"); - } - - fileName_ = file.Name; - } - - /// - /// Initializes a new instance of the class. - /// - /// The file. - public DiskArchiveStorage(ZipFile file) - : this(file, FileUpdateMode.Safe) - { - } - - #endregion Constructors - - #region IArchiveStorage Members - - /// - /// Gets a temporary output for performing updates on. - /// - /// Returns the temporary output stream. - public override Stream GetTemporaryOutput() - { - temporaryName_ = PathUtils.GetTempFileName(temporaryName_); - temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); - - return temporaryStream_; - } - - /// - /// Converts a temporary to its final form. - /// - /// Returns a that can be used to read - /// the final storage for the archive. - public override Stream ConvertTemporaryToFinal() - { - if (temporaryStream_ == null) - { - throw new ZipException("No temporary stream has been created"); - } - - Stream result = null; - - string moveTempName = PathUtils.GetTempFileName(fileName_); - bool newFileCreated = false; - - try - { - temporaryStream_.Dispose(); - File.Move(fileName_, moveTempName); - File.Move(temporaryName_, fileName_); - newFileCreated = true; - File.Delete(moveTempName); - - result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); - } - catch (Exception) - { - result = null; - - // Try to roll back changes... - if (!newFileCreated) - { - File.Move(moveTempName, fileName_); - File.Delete(temporaryName_); - } - - throw; - } - - return result; - } - - /// - /// Make a temporary copy of a stream. - /// - /// The to copy. - /// Returns a temporary output that is a copy of the input. - public override Stream MakeTemporaryCopy(Stream stream) - { - stream.Dispose(); - - temporaryName_ = PathUtils.GetTempFileName(fileName_); - File.Copy(fileName_, temporaryName_, true); - - temporaryStream_ = new FileStream(temporaryName_, - FileMode.Open, - FileAccess.ReadWrite); - return temporaryStream_; - } - - /// - /// Return a stream suitable for performing direct updates on the original source. - /// - /// The current stream. - /// Returns a stream suitable for direct updating. - /// If the is not null this is used as is. - public override Stream OpenForDirectUpdate(Stream stream) - { - Stream result; - if ((stream == null) || !stream.CanWrite) - { - if (stream != null) - { - stream.Dispose(); - } - - result = new FileStream(fileName_, - FileMode.Open, - FileAccess.ReadWrite); - } - else - { - result = stream; - } - - return result; - } - - /// - /// Disposes this instance. - /// - public override void Dispose() - { - if (temporaryStream_ != null) - { - temporaryStream_.Dispose(); - } - } - - #endregion IArchiveStorage Members - - #region Instance Fields - - private Stream temporaryStream_; - private readonly string fileName_; - private string temporaryName_; - - #endregion Instance Fields - } - - /// - /// An implementation suitable for in memory streams. - /// - public class MemoryArchiveStorage : BaseArchiveStorage - { - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - public MemoryArchiveStorage() - : base(FileUpdateMode.Direct) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The to use - /// This constructor is for testing as memory streams dont really require safe mode. - public MemoryArchiveStorage(FileUpdateMode updateMode) - : base(updateMode) - { - } - - #endregion Constructors - - #region Properties - - /// - /// Get the stream returned by if this was in fact called. - /// - public MemoryStream FinalStream - { - get { return finalStream_; } - } - - #endregion Properties - - #region IArchiveStorage Members - - /// - /// Gets the temporary output - /// - /// Returns the temporary output stream. - public override Stream GetTemporaryOutput() - { - temporaryStream_ = new MemoryStream(); - return temporaryStream_; - } - - /// - /// Converts the temporary to its final form. - /// - /// Returns a that can be used to read - /// the final storage for the archive. - public override Stream ConvertTemporaryToFinal() - { - if (temporaryStream_ == null) - { - throw new ZipException("No temporary stream has been created"); - } - - finalStream_ = new MemoryStream(temporaryStream_.ToArray()); - return finalStream_; - } - - /// - /// Make a temporary copy of the original stream. - /// - /// The to copy. - /// Returns a temporary output that is a copy of the input. - public override Stream MakeTemporaryCopy(Stream stream) - { - temporaryStream_ = new MemoryStream(); - stream.Position = 0; - StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); - return temporaryStream_; - } - - /// - /// Return a stream suitable for performing direct updates on the original source. - /// - /// The original source stream - /// Returns a stream suitable for direct updating. - /// If the passed is not null this is used; - /// otherwise a new is returned. - public override Stream OpenForDirectUpdate(Stream stream) - { - Stream result; - if ((stream == null) || !stream.CanWrite) - { - result = new MemoryStream(); - - if (stream != null) - { - stream.Position = 0; - StreamUtils.Copy(stream, result, new byte[4096]); - - stream.Dispose(); - } - } - else - { - result = stream; - } - - return result; - } - - /// - /// Disposes this instance. - /// - public override void Dispose() - { - if (temporaryStream_ != null) - { - temporaryStream_.Dispose(); - } - } - - #endregion IArchiveStorage Members - - #region Instance Fields - - private MemoryStream temporaryStream_; - private MemoryStream finalStream_; - - #endregion Instance Fields - } - - #endregion Archive Storage + /// + /// Get a data source. + /// + /// The to get a source for. + /// The name for data if known. + /// Returns a to use for compression input. + /// Ideally a new stream is created and opened to achieve this, to avoid locking problems. + Stream GetSource(ZipEntry entry, string name); } + +/// +/// Default implementation of a for use with files stored on disk. +/// +public class StaticDiskDataSource : IStaticDataSource +{ + /// + /// Initialise a new instance of + /// + /// The name of the file to obtain data from. + public StaticDiskDataSource(string fileName) + { + fileName_ = fileName; + } + + #region IDataSource Members + + /// + /// Get a providing data. + /// + /// Returns a providing data. + public Stream GetSource() + { + return File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); + } + + private readonly + + #endregion IDataSource Members + + #region Instance Fields + + string fileName_; + + #endregion Instance Fields +} + +/// +/// Default implementation of for files stored on disk. +/// +public class DynamicDiskDataSource : IDynamicDataSource +{ + #region IDataSource Members + + /// + /// Get a providing data for an entry. + /// + /// The entry to provide data for. + /// The file name for data if known. + /// Returns a stream providing data; or null if not available + public Stream GetSource(ZipEntry entry, string name) + { + Stream result = null; + + if (name != null) + { + result = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read); + } + + return result; + } + + #endregion IDataSource Members +} + +#endregion DataSources + +#region Archive Storage + +/// +/// Defines facilities for data storage when updating Zip Archives. +/// +public interface IArchiveStorage +{ + /// + /// Get the to apply during updates. + /// + FileUpdateMode UpdateMode { get; } + + /// + /// Get an empty that can be used for temporary output. + /// + /// Returns a temporary output + /// + Stream GetTemporaryOutput(); + + /// + /// Convert a temporary output stream to a final stream. + /// + /// The resulting final + /// + Stream ConvertTemporaryToFinal(); + + /// + /// Make a temporary copy of the original stream. + /// + /// The to copy. + /// Returns a temporary output that is a copy of the input. + Stream MakeTemporaryCopy(Stream stream); + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The current stream. + /// Returns a stream suitable for direct updating. + /// This may be the current stream passed. + Stream OpenForDirectUpdate(Stream stream); + + /// + /// Dispose of this instance. + /// + void Dispose(); +} + +/// +/// An abstract suitable for extension by inheritance. +/// +public abstract class BaseArchiveStorage : IArchiveStorage +{ + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The update mode. + protected BaseArchiveStorage(FileUpdateMode updateMode) + { + updateMode_ = updateMode; + } + + #endregion Constructors + + #region IArchiveStorage Members + + /// + /// Gets a temporary output + /// + /// Returns the temporary output stream. + /// + public abstract Stream GetTemporaryOutput(); + + /// + /// Converts the temporary to its final form. + /// + /// Returns a that can be used to read + /// the final storage for the archive. + /// + public abstract Stream ConvertTemporaryToFinal(); + + /// + /// Make a temporary copy of a . + /// + /// The to make a copy of. + /// Returns a temporary output that is a copy of the input. + public abstract Stream MakeTemporaryCopy(Stream stream); + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The to open for direct update. + /// Returns a stream suitable for direct updating. + public abstract Stream OpenForDirectUpdate(Stream stream); + + /// + /// Disposes this instance. + /// + public abstract void Dispose(); + + /// + /// Gets the update mode applicable. + /// + /// The update mode. + public FileUpdateMode UpdateMode + { + get + { + return updateMode_; + } + } + + #endregion IArchiveStorage Members + + #region Instance Fields + + private readonly FileUpdateMode updateMode_; + + #endregion Instance Fields +} + +/// +/// An implementation suitable for hard disks. +/// +public class DiskArchiveStorage : BaseArchiveStorage +{ + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The file. + /// The update mode. + public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode) + : base(updateMode) + { + if (file.Name == null) + { + throw new ZipException("Cant handle non file archives"); + } + + fileName_ = file.Name; + } + + /// + /// Initializes a new instance of the class. + /// + /// The file. + public DiskArchiveStorage(ZipFile file) + : this(file, FileUpdateMode.Safe) + { + } + + #endregion Constructors + + #region IArchiveStorage Members + + /// + /// Gets a temporary output for performing updates on. + /// + /// Returns the temporary output stream. + public override Stream GetTemporaryOutput() + { + temporaryName_ = PathUtils.GetTempFileName(temporaryName_); + temporaryStream_ = File.Open(temporaryName_, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + + return temporaryStream_; + } + + /// + /// Converts a temporary to its final form. + /// + /// Returns a that can be used to read + /// the final storage for the archive. + public override Stream ConvertTemporaryToFinal() + { + if (temporaryStream_ == null) + { + throw new ZipException("No temporary stream has been created"); + } + + var moveTempName = PathUtils.GetTempFileName(fileName_); + var newFileCreated = false; + + + Stream result; + try + { + temporaryStream_.Dispose(); + File.Move(fileName_, moveTempName); + File.Move(temporaryName_, fileName_); + newFileCreated = true; + File.Delete(moveTempName); + + result = File.Open(fileName_, FileMode.Open, FileAccess.Read, FileShare.Read); + } + catch (Exception) + { + + // Try to roll back changes... + if (!newFileCreated) + { + File.Move(moveTempName, fileName_); + File.Delete(temporaryName_); + } + + throw; + } + + return result; + } + + /// + /// Make a temporary copy of a stream. + /// + /// The to copy. + /// Returns a temporary output that is a copy of the input. + public override Stream MakeTemporaryCopy(Stream stream) + { + stream.Dispose(); + + temporaryName_ = PathUtils.GetTempFileName(fileName_); + File.Copy(fileName_, temporaryName_, true); + + temporaryStream_ = new FileStream(temporaryName_, + FileMode.Open, + FileAccess.ReadWrite); + return temporaryStream_; + } + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The current stream. + /// Returns a stream suitable for direct updating. + /// If the is not null this is used as is. + public override Stream OpenForDirectUpdate(Stream stream) + { + Stream result; + if ((stream == null) || !stream.CanWrite) + { + stream?.Dispose(); + + result = new FileStream(fileName_, + FileMode.Open, + FileAccess.ReadWrite); + } + else + { + result = stream; + } + + return result; + } + + /// + /// Disposes this instance. + /// + public override void Dispose() + { + temporaryStream_?.Dispose(); + } + + #endregion IArchiveStorage Members + + #region Instance Fields + + private Stream temporaryStream_; + private readonly string fileName_; + private string temporaryName_; + + #endregion Instance Fields +} + +/// +/// An implementation suitable for in memory streams. +/// +public class MemoryArchiveStorage : BaseArchiveStorage +{ + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public MemoryArchiveStorage() + : base(FileUpdateMode.Direct) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use + /// This constructor is for testing as memory streams dont really require safe mode. + public MemoryArchiveStorage(FileUpdateMode updateMode) + : base(updateMode) + { + } + + #endregion Constructors + + #region Properties + + /// + /// Get the stream returned by if this was in fact called. + /// + public MemoryStream FinalStream + { + get { return finalStream_; } + } + + #endregion Properties + + #region IArchiveStorage Members + + /// + /// Gets the temporary output + /// + /// Returns the temporary output stream. + public override Stream GetTemporaryOutput() + { + temporaryStream_ = new MemoryStream(); + return temporaryStream_; + } + + /// + /// Converts the temporary to its final form. + /// + /// Returns a that can be used to read + /// the final storage for the archive. + public override Stream ConvertTemporaryToFinal() + { + if (temporaryStream_ == null) + { + throw new ZipException("No temporary stream has been created"); + } + + finalStream_ = new MemoryStream(temporaryStream_.ToArray()); + return finalStream_; + } + + /// + /// Make a temporary copy of the original stream. + /// + /// The to copy. + /// Returns a temporary output that is a copy of the input. + public override Stream MakeTemporaryCopy(Stream stream) + { + temporaryStream_ = new MemoryStream(); + stream.Position = 0; + StreamUtils.Copy(stream, temporaryStream_, new byte[4096]); + return temporaryStream_; + } + + /// + /// Return a stream suitable for performing direct updates on the original source. + /// + /// The original source stream + /// Returns a stream suitable for direct updating. + /// If the passed is not null this is used; + /// otherwise a new is returned. + public override Stream OpenForDirectUpdate(Stream stream) + { + Stream result; + if ((stream == null) || !stream.CanWrite) + { + result = new MemoryStream(); + + if (stream != null) + { + stream.Position = 0; + StreamUtils.Copy(stream, result, new byte[4096]); + + stream.Dispose(); + } + } + else + { + result = stream; + } + + return result; + } + + /// + /// Disposes this instance. + /// + public override void Dispose() + { + temporaryStream_?.Dispose(); + } + + #endregion IArchiveStorage Members + + #region Instance Fields + + private MemoryStream temporaryStream_; + private MemoryStream finalStream_; + + #endregion Instance Fields +} + +#endregion Archive Storage diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs index 33a4add37..1954a634a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs @@ -1,629 +1,623 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// Holds data pertinent to a data descriptor. +/// +public class DescriptorData +{ + /// + /// Get /set the compressed size of data. + /// + public long CompressedSize + { + get { return compressedSize; } + set { compressedSize = value; } + } + + /// + /// Get / set the uncompressed size of data + /// + public long Size + { + get { return size; } + set { size = value; } + } + + /// + /// Get /set the crc value. + /// + public long Crc + { + get { return crc; } + set { crc = value & 0xffffffff; } + } + + #region Instance Fields + + private long size; + private long compressedSize; + private long crc; + + #endregion Instance Fields +} + +internal class EntryPatchData +{ + public long SizePatchOffset + { + get { return sizePatchOffset_; } + set { sizePatchOffset_ = value; } + } + + public long CrcPatchOffset + { + get { return crcPatchOffset_; } + set { crcPatchOffset_ = value; } + } + + #region Instance Fields + + private long sizePatchOffset_; + private long crcPatchOffset_; + + #endregion Instance Fields +} + +/// +/// This class assists with writing/reading from Zip files. +/// +internal class ZipHelperStream : Stream { - /// - /// Holds data pertinent to a data descriptor. - /// - public class DescriptorData - { - /// - /// Get /set the compressed size of data. - /// - public long CompressedSize - { - get { return compressedSize; } - set { compressedSize = value; } - } - - /// - /// Get / set the uncompressed size of data - /// - public long Size - { - get { return size; } - set { size = value; } - } - - /// - /// Get /set the crc value. - /// - public long Crc - { - get { return crc; } - set { crc = (value & 0xffffffff); } - } - - #region Instance Fields - - private long size; - private long compressedSize; - private long crc; - - #endregion Instance Fields - } - - internal class EntryPatchData - { - public long SizePatchOffset - { - get { return sizePatchOffset_; } - set { sizePatchOffset_ = value; } - } - - public long CrcPatchOffset - { - get { return crcPatchOffset_; } - set { crcPatchOffset_ = value; } - } - - #region Instance Fields - - private long sizePatchOffset_; - private long crcPatchOffset_; - - #endregion Instance Fields - } - - /// - /// This class assists with writing/reading from Zip files. - /// - internal class ZipHelperStream : Stream - { - #region Constructors - - /// - /// Initialise an instance of this class. - /// - /// The name of the file to open. - public ZipHelperStream(string name) - { - stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); - isOwner_ = true; - } - - /// - /// Initialise a new instance of . - /// - /// The stream to use. - public ZipHelperStream(Stream stream) - { - stream_ = stream; - } - - #endregion Constructors - - /// - /// Get / set a value indicating whether the underlying stream is owned or not. - /// - /// If the stream is owned it is closed when this instance is closed. - public bool IsStreamOwner - { - get { return isOwner_; } - set { isOwner_ = value; } - } - - #region Base Stream Methods - - public override bool CanRead - { - get { return stream_.CanRead; } - } - - public override bool CanSeek - { - get { return stream_.CanSeek; } - } - - public override bool CanTimeout - { - get { return stream_.CanTimeout; } - } - - public override long Length - { - get { return stream_.Length; } - } - - public override long Position - { - get { return stream_.Position; } - set { stream_.Position = value; } - } - - public override bool CanWrite - { - get { return stream_.CanWrite; } - } - - public override void Flush() - { - stream_.Flush(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return stream_.Seek(offset, origin); - } - - public override void SetLength(long value) - { - stream_.SetLength(value); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return stream_.Read(buffer, offset, count); - } - - public override void Write(byte[] buffer, int offset, int count) - { - stream_.Write(buffer, offset, count); - } - - /// - /// Close the stream. - /// - /// - /// The underlying stream is closed only if is true. - /// - protected override void Dispose(bool disposing) - { - Stream toClose = stream_; - stream_ = null; - if (isOwner_ && (toClose != null)) - { - isOwner_ = false; - toClose.Dispose(); - } - } - - #endregion Base Stream Methods - - // Write the local file header - // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage - private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) - { - CompressionMethod method = entry.CompressionMethod; - bool headerInfoAvailable = true; // How to get this? - bool patchEntryHeader = false; - - WriteLEInt(ZipConstants.LocalHeaderSignature); - - WriteLEShort(entry.Version); - WriteLEShort(entry.Flags); - WriteLEShort((byte)method); - WriteLEInt((int)entry.DosTime); - - if (headerInfoAvailable == true) - { - WriteLEInt((int)entry.Crc); - if (entry.LocalHeaderRequiresZip64) - { - WriteLEInt(-1); - WriteLEInt(-1); - } - else - { - WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); - WriteLEInt((int)entry.Size); - } - } - else - { - if (patchData != null) - { - patchData.CrcPatchOffset = stream_.Position; - } - WriteLEInt(0); // Crc - - if (patchData != null) - { - patchData.SizePatchOffset = stream_.Position; - } - - // For local header both sizes appear in Zip64 Extended Information - if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) - { - WriteLEInt(-1); - WriteLEInt(-1); - } - else - { - WriteLEInt(0); // Compressed size - WriteLEInt(0); // Uncompressed size - } - } - - byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); - - if (name.Length > 0xFFFF) - { - throw new ZipException("Entry name too long."); - } - - var ed = new ZipExtraData(entry.ExtraData); - - if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) - { - ed.StartNewEntry(); - if (headerInfoAvailable) - { - ed.AddLeLong(entry.Size); - ed.AddLeLong(entry.CompressedSize); - } - else - { - ed.AddLeLong(-1); - ed.AddLeLong(-1); - } - ed.AddNewEntry(1); - - if (!ed.Find(1)) - { - throw new ZipException("Internal error cant find extra data"); - } - - if (patchData != null) - { - patchData.SizePatchOffset = ed.CurrentReadIndex; - } - } - else - { - ed.Delete(1); - } - - byte[] extra = ed.GetEntryData(); - - WriteLEShort(name.Length); - WriteLEShort(extra.Length); - - if (name.Length > 0) - { - stream_.Write(name, 0, name.Length); - } - - if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) - { - patchData.SizePatchOffset += stream_.Position; - } - - if (extra.Length > 0) - { - stream_.Write(extra, 0, extra.Length); - } - } - - /// - /// Locates a block with the desired . - /// - /// The signature to find. - /// Location, marking the end of block. - /// Minimum size of the block. - /// The maximum variable data. - /// Returns the offset of the first byte after the signature; -1 if not found - public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) - { - long pos = endLocation - minimumBlockSize; - if (pos < 0) - { - return -1; - } - - long giveUpMarker = Math.Max(pos - maximumVariableData, 0); - - // TODO: This loop could be optimised for speed. - do - { - if (pos < giveUpMarker) - { - return -1; - } - Seek(pos--, SeekOrigin.Begin); - } while (ReadLEInt() != signature); - - return Position; - } - - /// - /// Write Zip64 end of central directory records (File header and locator). - /// - /// The number of entries in the central directory. - /// The size of entries in the central directory. - /// The offset of the central directory. - public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset) - { - long centralSignatureOffset = centralDirOffset + sizeEntries; - WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature); - WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12) - WriteLEShort(ZipConstants.VersionMadeBy); // Version made by - WriteLEShort(ZipConstants.VersionZip64); // Version to extract - WriteLEInt(0); // Number of this disk - WriteLEInt(0); // number of the disk with the start of the central directory - WriteLELong(noOfEntries); // No of entries on this disk - WriteLELong(noOfEntries); // Total No of entries in central directory - WriteLELong(sizeEntries); // Size of the central directory - WriteLELong(centralDirOffset); // offset of start of central directory - // zip64 extensible data sector not catered for here (variable size) - - // Write the Zip64 end of central directory locator - WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature); - - // no of the disk with the start of the zip64 end of central directory - WriteLEInt(0); - - // relative offset of the zip64 end of central directory record - WriteLELong(centralSignatureOffset); - - // total number of disks - WriteLEInt(1); - } - - /// - /// Write the required records to end the central directory. - /// - /// The number of entries in the directory. - /// The size of the entries in the directory. - /// The start of the central directory. - /// The archive comment. (This can be null). - public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries, - long startOfCentralDirectory, byte[] comment) - { - if ((noOfEntries >= 0xffff) || - (startOfCentralDirectory >= 0xffffffff) || - (sizeEntries >= 0xffffffff)) - { - WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory); - } - - WriteLEInt(ZipConstants.EndOfCentralDirectorySignature); - - // TODO: ZipFile Multi disk handling not done - WriteLEShort(0); // number of this disk - WriteLEShort(0); // no of disk with start of central dir - - // Number of entries - if (noOfEntries >= 0xffff) - { - WriteLEUshort(0xffff); // Zip64 marker - WriteLEUshort(0xffff); - } - else - { - WriteLEShort((short)noOfEntries); // entries in central dir for this disk - WriteLEShort((short)noOfEntries); // total entries in central directory - } - - // Size of the central directory - if (sizeEntries >= 0xffffffff) - { - WriteLEUint(0xffffffff); // Zip64 marker - } - else - { - WriteLEInt((int)sizeEntries); - } - - // offset of start of central directory - if (startOfCentralDirectory >= 0xffffffff) - { - WriteLEUint(0xffffffff); // Zip64 marker - } - else - { - WriteLEInt((int)startOfCentralDirectory); - } - - int commentLength = (comment != null) ? comment.Length : 0; - - if (commentLength > 0xffff) - { - throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength)); - } - - WriteLEShort(commentLength); - - if (commentLength > 0) - { - Write(comment, 0, comment.Length); - } - } - - #region LE value reading/writing - - /// - /// Read an unsigned short in little endian byte order. - /// - /// Returns the value read. - /// - /// An i/o error occurs. - /// - /// - /// The file ends prematurely - /// - public int ReadLEShort() - { - int byteValue1 = stream_.ReadByte(); - - if (byteValue1 < 0) - { - throw new EndOfStreamException(); - } - - int byteValue2 = stream_.ReadByte(); - if (byteValue2 < 0) - { - throw new EndOfStreamException(); - } - - return byteValue1 | (byteValue2 << 8); - } - - /// - /// Read an int in little endian byte order. - /// - /// Returns the value read. - /// - /// An i/o error occurs. - /// - /// - /// The file ends prematurely - /// - public int ReadLEInt() - { - return ReadLEShort() | (ReadLEShort() << 16); - } - - /// - /// Read a long in little endian byte order. - /// - /// The value read. - public long ReadLELong() - { - return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); - } - - /// - /// Write an unsigned short in little endian byte order. - /// - /// The value to write. - public void WriteLEShort(int value) - { - stream_.WriteByte((byte)(value & 0xff)); - stream_.WriteByte((byte)((value >> 8) & 0xff)); - } - - /// - /// Write a ushort in little endian byte order. - /// - /// The value to write. - public void WriteLEUshort(ushort value) - { - stream_.WriteByte((byte)(value & 0xff)); - stream_.WriteByte((byte)(value >> 8)); - } - - /// - /// Write an int in little endian byte order. - /// - /// The value to write. - public void WriteLEInt(int value) - { - WriteLEShort(value); - WriteLEShort(value >> 16); - } - - /// - /// Write a uint in little endian byte order. - /// - /// The value to write. - public void WriteLEUint(uint value) - { - WriteLEUshort((ushort)(value & 0xffff)); - WriteLEUshort((ushort)(value >> 16)); - } - - /// - /// Write a long in little endian byte order. - /// - /// The value to write. - public void WriteLELong(long value) - { - WriteLEInt((int)value); - WriteLEInt((int)(value >> 32)); - } - - /// - /// Write a ulong in little endian byte order. - /// - /// The value to write. - public void WriteLEUlong(ulong value) - { - WriteLEUint((uint)(value & 0xffffffff)); - WriteLEUint((uint)(value >> 32)); - } - - #endregion LE value reading/writing - - /// - /// Write a data descriptor. - /// - /// The entry to write a descriptor for. - /// Returns the number of descriptor bytes written. - public int WriteDataDescriptor(ZipEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - int result = 0; - - // Add data descriptor if flagged as required - if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) - { - // The signature is not PKZIP originally but is now described as optional - // in the PKZIP Appnote documenting the format. - WriteLEInt(ZipConstants.DataDescriptorSignature); - WriteLEInt(unchecked((int)(entry.Crc))); - - result += 8; - - if (entry.LocalHeaderRequiresZip64) - { - WriteLELong(entry.CompressedSize); - WriteLELong(entry.Size); - result += 16; - } - else - { - WriteLEInt((int)entry.CompressedSize); - WriteLEInt((int)entry.Size); - result += 8; - } - } - - return result; - } - - /// - /// Read data descriptor at the end of compressed data. - /// - /// if set to true [zip64]. - /// The data to fill in. - /// Returns the number of bytes read in the descriptor. - public void ReadDataDescriptor(bool zip64, DescriptorData data) - { - int intValue = ReadLEInt(); - - // In theory this may not be a descriptor according to PKZIP appnote. - // In practice its always there. - if (intValue != ZipConstants.DataDescriptorSignature) - { - throw new ZipException("Data descriptor signature not found"); - } - - data.Crc = ReadLEInt(); - - if (zip64) - { - data.CompressedSize = ReadLELong(); - data.Size = ReadLELong(); - } - else - { - data.CompressedSize = ReadLEInt(); - data.Size = ReadLEInt(); - } - } - - #region Instance Fields - - private bool isOwner_; - private Stream stream_; - - #endregion Instance Fields - } + #region Constructors + + /// + /// Initialise an instance of this class. + /// + /// The name of the file to open. + public ZipHelperStream(string name) + { + stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); + isOwner_ = true; + } + + /// + /// Initialise a new instance of . + /// + /// The stream to use. + public ZipHelperStream(Stream stream) + { + stream_ = stream; + } + + #endregion Constructors + + /// + /// Get / set a value indicating whether the underlying stream is owned or not. + /// + /// If the stream is owned it is closed when this instance is closed. + public bool IsStreamOwner + { + get { return isOwner_; } + set { isOwner_ = value; } + } + + #region Base Stream Methods + + public override bool CanRead + { + get { return stream_.CanRead; } + } + + public override bool CanSeek + { + get { return stream_.CanSeek; } + } + + public override bool CanTimeout + { + get { return stream_.CanTimeout; } + } + + public override long Length + { + get { return stream_.Length; } + } + + public override long Position + { + get { return stream_.Position; } + set { stream_.Position = value; } + } + + public override bool CanWrite + { + get { return stream_.CanWrite; } + } + + public override void Flush() + { + stream_.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return stream_.Seek(offset, origin); + } + + public override void SetLength(long value) + { + stream_.SetLength(value); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return stream_.Read(buffer, offset, count); + } + + public override void Write(byte[] buffer, int offset, int count) + { + stream_.Write(buffer, offset, count); + } + + /// + /// Close the stream. + /// + /// + /// The underlying stream is closed only if is true. + /// + protected override void Dispose(bool disposing) + { + var toClose = stream_; + stream_ = null; + if (isOwner_ && (toClose != null)) + { + isOwner_ = false; + toClose.Dispose(); + } + } + + #endregion Base Stream Methods + + // Write the local file header + // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage + private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) + { + var method = entry.CompressionMethod; + var headerInfoAvailable = true; // How to get this? + var patchEntryHeader = false; + + WriteLEInt(ZipConstants.LocalHeaderSignature); + + WriteLEShort(entry.Version); + WriteLEShort(entry.Flags); + WriteLEShort((byte)method); + WriteLEInt((int)entry.DosTime); + + if (headerInfoAvailable == true) + { + WriteLEInt((int)entry.Crc); + if (entry.LocalHeaderRequiresZip64) + { + WriteLEInt(-1); + WriteLEInt(-1); + } + else + { + WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); + WriteLEInt((int)entry.Size); + } + } + else + { + if (patchData != null) + { + patchData.CrcPatchOffset = stream_.Position; + } + WriteLEInt(0); // Crc + + if (patchData != null) + { + patchData.SizePatchOffset = stream_.Position; + } + + // For local header both sizes appear in Zip64 Extended Information + if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) + { + WriteLEInt(-1); + WriteLEInt(-1); + } + else + { + WriteLEInt(0); // Compressed size + WriteLEInt(0); // Uncompressed size + } + } + + var name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) + { + ed.StartNewEntry(); + if (headerInfoAvailable) + { + ed.AddLeLong(entry.Size); + ed.AddLeLong(entry.CompressedSize); + } + else + { + ed.AddLeLong(-1); + ed.AddLeLong(-1); + } + ed.AddNewEntry(1); + + if (!ed.Find(1)) + { + throw new ZipException("Internal error cant find extra data"); + } + + if (patchData != null) + { + patchData.SizePatchOffset = ed.CurrentReadIndex; + } + } + else + { + ed.Delete(1); + } + + var extra = ed.GetEntryData(); + + WriteLEShort(name.Length); + WriteLEShort(extra.Length); + + if (name.Length > 0) + { + stream_.Write(name, 0, name.Length); + } + + if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) + { + patchData.SizePatchOffset += stream_.Position; + } + + if (extra.Length > 0) + { + stream_.Write(extra, 0, extra.Length); + } + } + + /// + /// Locates a block with the desired . + /// + /// The signature to find. + /// Location, marking the end of block. + /// Minimum size of the block. + /// The maximum variable data. + /// Returns the offset of the first byte after the signature; -1 if not found + public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) + { + var pos = endLocation - minimumBlockSize; + if (pos < 0) + { + return -1; + } + + var giveUpMarker = Math.Max(pos - maximumVariableData, 0); + + // TODO: This loop could be optimised for speed. + do + { + if (pos < giveUpMarker) + { + return -1; + } + Seek(pos--, SeekOrigin.Begin); + } while (ReadLEInt() != signature); + + return Position; + } + + /// + /// Write Zip64 end of central directory records (File header and locator). + /// + /// The number of entries in the central directory. + /// The size of entries in the central directory. + /// The offset of the central directory. + public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset) + { + var centralSignatureOffset = centralDirOffset + sizeEntries; + WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature); + WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12) + WriteLEShort(ZipConstants.VersionMadeBy); // Version made by + WriteLEShort(ZipConstants.VersionZip64); // Version to extract + WriteLEInt(0); // Number of this disk + WriteLEInt(0); // number of the disk with the start of the central directory + WriteLELong(noOfEntries); // No of entries on this disk + WriteLELong(noOfEntries); // Total No of entries in central directory + WriteLELong(sizeEntries); // Size of the central directory + WriteLELong(centralDirOffset); // offset of start of central directory + // zip64 extensible data sector not catered for here (variable size) + + // Write the Zip64 end of central directory locator + WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature); + + // no of the disk with the start of the zip64 end of central directory + WriteLEInt(0); + + // relative offset of the zip64 end of central directory record + WriteLELong(centralSignatureOffset); + + // total number of disks + WriteLEInt(1); + } + + /// + /// Write the required records to end the central directory. + /// + /// The number of entries in the directory. + /// The size of the entries in the directory. + /// The start of the central directory. + /// The archive comment. (This can be null). + public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries, + long startOfCentralDirectory, byte[] comment) + { + if ((noOfEntries >= 0xffff) || + (startOfCentralDirectory >= 0xffffffff) || + (sizeEntries >= 0xffffffff)) + { + WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory); + } + + WriteLEInt(ZipConstants.EndOfCentralDirectorySignature); + + // TODO: ZipFile Multi disk handling not done + WriteLEShort(0); // number of this disk + WriteLEShort(0); // no of disk with start of central dir + + // Number of entries + if (noOfEntries >= 0xffff) + { + WriteLEUshort(0xffff); // Zip64 marker + WriteLEUshort(0xffff); + } + else + { + WriteLEShort((short)noOfEntries); // entries in central dir for this disk + WriteLEShort((short)noOfEntries); // total entries in central directory + } + + // Size of the central directory + if (sizeEntries >= 0xffffffff) + { + WriteLEUint(0xffffffff); // Zip64 marker + } + else + { + WriteLEInt((int)sizeEntries); + } + + // offset of start of central directory + if (startOfCentralDirectory >= 0xffffffff) + { + WriteLEUint(0xffffffff); // Zip64 marker + } + else + { + WriteLEInt((int)startOfCentralDirectory); + } + + var commentLength = (comment != null) ? comment.Length : 0; + + if (commentLength > 0xffff) + { + throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength)); + } + + WriteLEShort(commentLength); + + if (commentLength > 0) + { + Write(comment, 0, comment.Length); + } + } + + #region LE value reading/writing + + /// + /// Read an unsigned short in little endian byte order. + /// + /// Returns the value read. + /// + /// An i/o error occurs. + /// + /// + /// The file ends prematurely + /// + public int ReadLEShort() + { + var byteValue1 = stream_.ReadByte(); + + if (byteValue1 < 0) + { + throw new EndOfStreamException(); + } + + var byteValue2 = stream_.ReadByte(); + return byteValue2 < 0 ? throw new EndOfStreamException() : byteValue1 | (byteValue2 << 8); + } + + /// + /// Read an int in little endian byte order. + /// + /// Returns the value read. + /// + /// An i/o error occurs. + /// + /// + /// The file ends prematurely + /// + public int ReadLEInt() + { + return ReadLEShort() | (ReadLEShort() << 16); + } + + /// + /// Read a long in little endian byte order. + /// + /// The value read. + public long ReadLELong() + { + return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); + } + + /// + /// Write an unsigned short in little endian byte order. + /// + /// The value to write. + public void WriteLEShort(int value) + { + stream_.WriteByte((byte)(value & 0xff)); + stream_.WriteByte((byte)((value >> 8) & 0xff)); + } + + /// + /// Write a ushort in little endian byte order. + /// + /// The value to write. + public void WriteLEUshort(ushort value) + { + stream_.WriteByte((byte)(value & 0xff)); + stream_.WriteByte((byte)(value >> 8)); + } + + /// + /// Write an int in little endian byte order. + /// + /// The value to write. + public void WriteLEInt(int value) + { + WriteLEShort(value); + WriteLEShort(value >> 16); + } + + /// + /// Write a uint in little endian byte order. + /// + /// The value to write. + public void WriteLEUint(uint value) + { + WriteLEUshort((ushort)(value & 0xffff)); + WriteLEUshort((ushort)(value >> 16)); + } + + /// + /// Write a long in little endian byte order. + /// + /// The value to write. + public void WriteLELong(long value) + { + WriteLEInt((int)value); + WriteLEInt((int)(value >> 32)); + } + + /// + /// Write a ulong in little endian byte order. + /// + /// The value to write. + public void WriteLEUlong(ulong value) + { + WriteLEUint((uint)(value & 0xffffffff)); + WriteLEUint((uint)(value >> 32)); + } + + #endregion LE value reading/writing + + /// + /// Write a data descriptor. + /// + /// The entry to write a descriptor for. + /// Returns the number of descriptor bytes written. + public int WriteDataDescriptor(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + var result = 0; + + // Add data descriptor if flagged as required + if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) + { + // The signature is not PKZIP originally but is now described as optional + // in the PKZIP Appnote documenting the format. + WriteLEInt(ZipConstants.DataDescriptorSignature); + WriteLEInt(unchecked((int)entry.Crc)); + + result += 8; + + if (entry.LocalHeaderRequiresZip64) + { + WriteLELong(entry.CompressedSize); + WriteLELong(entry.Size); + result += 16; + } + else + { + WriteLEInt((int)entry.CompressedSize); + WriteLEInt((int)entry.Size); + result += 8; + } + } + + return result; + } + + /// + /// Read data descriptor at the end of compressed data. + /// + /// if set to true [zip64]. + /// The data to fill in. + /// Returns the number of bytes read in the descriptor. + public void ReadDataDescriptor(bool zip64, DescriptorData data) + { + var intValue = ReadLEInt(); + + // In theory this may not be a descriptor according to PKZIP appnote. + // In practice its always there. + if (intValue != ZipConstants.DataDescriptorSignature) + { + throw new ZipException("Data descriptor signature not found"); + } + + data.Crc = ReadLEInt(); + + if (zip64) + { + data.CompressedSize = ReadLELong(); + data.Size = ReadLELong(); + } + else + { + data.CompressedSize = ReadLEInt(); + data.Size = ReadLEInt(); + } + } + + #region Instance Fields + + private bool isOwner_; + private Stream stream_; + + #endregion Instance Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs index c3e42a93b..74a94fe8a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs @@ -5,723 +5,701 @@ using System; using System.IO; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// This is an InflaterInputStream that reads the files baseInputStream an zip archive +/// one after another. It has a special method to get the zip entry of +/// the next file. The zip entry contains information about the file name +/// size, compressed size, Crc, etc. +/// It includes support for Stored and Deflated entries. +///
+///
Author of the original java version : Jochen Hoenicke +///
+/// +/// This sample shows how to read a zip file +/// +/// using System; +/// using System.Text; +/// using System.IO; +/// +/// using MelonLoader.ICSharpCode.SharpZipLib.Zip; +/// +/// class MainClass +/// { +/// public static void Main(string[] args) +/// { +/// using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) { +/// +/// ZipEntry theEntry; +/// const int size = 2048; +/// byte[] data = new byte[2048]; +/// +/// while ((theEntry = s.GetNextEntry()) != null) { +/// if ( entry.IsFile ) { +/// Console.Write("Show contents (y/n) ?"); +/// if (Console.ReadLine() == "y") { +/// while (true) { +/// size = s.Read(data, 0, data.Length); +/// if (size > 0) { +/// Console.Write(new ASCIIEncoding().GetString(data, 0, size)); +/// } else { +/// break; +/// } +/// } +/// } +/// } +/// } +/// } +/// } +/// } +/// +/// +public class ZipInputStream : InflaterInputStream { - /// - /// This is an InflaterInputStream that reads the files baseInputStream an zip archive - /// one after another. It has a special method to get the zip entry of - /// the next file. The zip entry contains information about the file name - /// size, compressed size, Crc, etc. - /// It includes support for Stored and Deflated entries. - ///
- ///
Author of the original java version : Jochen Hoenicke - ///
- /// - /// This sample shows how to read a zip file - /// - /// using System; - /// using System.Text; - /// using System.IO; - /// - /// using MelonLoader.ICSharpCode.SharpZipLib.Zip; - /// - /// class MainClass - /// { - /// public static void Main(string[] args) - /// { - /// using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) { - /// - /// ZipEntry theEntry; - /// const int size = 2048; - /// byte[] data = new byte[2048]; - /// - /// while ((theEntry = s.GetNextEntry()) != null) { - /// if ( entry.IsFile ) { - /// Console.Write("Show contents (y/n) ?"); - /// if (Console.ReadLine() == "y") { - /// while (true) { - /// size = s.Read(data, 0, data.Length); - /// if (size > 0) { - /// Console.Write(new ASCIIEncoding().GetString(data, 0, size)); - /// } else { - /// break; - /// } - /// } - /// } - /// } - /// } - /// } - /// } - /// } - /// - /// - public class ZipInputStream : InflaterInputStream - { - #region Instance Fields - - /// - /// Delegate for reading bytes from a stream. - /// - private delegate int ReadDataHandler(byte[] b, int offset, int length); - - /// - /// The current reader this instance. - /// - private ReadDataHandler internalReader; - - private Crc32 crc = new Crc32(); - private ZipEntry entry; - - private long size; - private CompressionMethod method; - private int flags; - private string password; - - #endregion Instance Fields - - #region Constructors - - /// - /// Creates a new Zip input stream, for reading a zip archive. - /// - /// The underlying providing data. - public ZipInputStream(Stream baseInputStream) - : base(baseInputStream, new Inflater(true)) - { - internalReader = new ReadDataHandler(ReadingNotAvailable); - } - - /// - /// Creates a new Zip input stream, for reading a zip archive. - /// - /// The underlying providing data. - /// Size of the buffer. - public ZipInputStream(Stream baseInputStream, int bufferSize) - : base(baseInputStream, new Inflater(true), bufferSize) - { - internalReader = new ReadDataHandler(ReadingNotAvailable); - } - - #endregion Constructors - - /// - /// Optional password used for encryption when non-null - /// - /// A password for all encrypted entries in this - public string Password - { - get - { - return password; - } - set - { - password = value; - } - } - - /// - /// Gets a value indicating if there is a current entry and it can be decompressed - /// - /// - /// The entry can only be decompressed if the library supports the zip features required to extract it. - /// See the ZipEntry Version property for more details. - /// - /// Since uses the local headers for extraction, entries with no compression combined with the - /// flag set, cannot be extracted as the end of the entry data cannot be deduced. - /// - public bool CanDecompressEntry - => entry != null - && IsEntryCompressionMethodSupported(entry) - && entry.CanDecompress - && (!entry.HasFlag(GeneralBitFlags.Descriptor) || entry.CompressionMethod != CompressionMethod.Stored || entry.IsCrypted); - - /// - /// Is the compression method for the specified entry supported? - /// - /// - /// Uses entry.CompressionMethodForHeader so that entries of type WinZipAES will be rejected. - /// - /// the entry to check. - /// true if the compression method is supported, false if not. - private static bool IsEntryCompressionMethodSupported(ZipEntry entry) - { - var entryCompressionMethod = entry.CompressionMethodForHeader; - - return entryCompressionMethod == CompressionMethod.Deflated || - entryCompressionMethod == CompressionMethod.Stored; - } - - /// - /// Advances to the next entry in the archive - /// - /// - /// The next entry in the archive or null if there are no more entries. - /// - /// - /// If the previous entry is still open CloseEntry is called. - /// - /// - /// Input stream is closed - /// - /// - /// Password is not set, password is invalid, compression method is invalid, - /// version required to extract is not supported - /// - public ZipEntry GetNextEntry() - { - if (crc == null) - { - throw new InvalidOperationException("Closed."); - } - - if (entry != null) - { - CloseEntry(); - } - - int header = inputBuffer.ReadLeInt(); - - if (header == ZipConstants.CentralHeaderSignature || - header == ZipConstants.EndOfCentralDirectorySignature || - header == ZipConstants.CentralHeaderDigitalSignature || - header == ZipConstants.ArchiveExtraDataSignature || - header == ZipConstants.Zip64CentralFileHeaderSignature) - { - // No more individual entries exist - Dispose(); - return null; - } - - // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found - // Spanning signature is same as descriptor signature and is untested as yet. - if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature)) - { - header = inputBuffer.ReadLeInt(); - } - - if (header != ZipConstants.LocalHeaderSignature) - { - throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header)); - } - - var versionRequiredToExtract = (short)inputBuffer.ReadLeShort(); - - flags = inputBuffer.ReadLeShort(); - method = (CompressionMethod)inputBuffer.ReadLeShort(); - var dostime = (uint)inputBuffer.ReadLeInt(); - int crc2 = inputBuffer.ReadLeInt(); - csize = inputBuffer.ReadLeInt(); - size = inputBuffer.ReadLeInt(); - int nameLen = inputBuffer.ReadLeShort(); - int extraLen = inputBuffer.ReadLeShort(); - - bool isCrypted = (flags & 1) == 1; - - byte[] buffer = new byte[nameLen]; - inputBuffer.ReadRawBuffer(buffer); - - string name = ZipStrings.ConvertToStringExt(flags, buffer); - - entry = new ZipEntry(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, method) - { - Flags = flags, - }; - - if ((flags & 8) == 0) - { - entry.Crc = crc2 & 0xFFFFFFFFL; - entry.Size = size & 0xFFFFFFFFL; - entry.CompressedSize = csize & 0xFFFFFFFFL; - - entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff); - } - else - { - // This allows for GNU, WinZip and possibly other archives, the PKZIP spec - // says these values are zero under these circumstances. - if (crc2 != 0) - { - entry.Crc = crc2 & 0xFFFFFFFFL; - } - - if (size != 0) - { - entry.Size = size & 0xFFFFFFFFL; - } - - if (csize != 0) - { - entry.CompressedSize = csize & 0xFFFFFFFFL; - } - - entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); - } - - entry.DosTime = dostime; - - // If local header requires Zip64 is true then the extended header should contain - // both values. - - // Handle extra data if present. This can set/alter some fields of the entry. - if (extraLen > 0) - { - byte[] extra = new byte[extraLen]; - inputBuffer.ReadRawBuffer(extra); - entry.ExtraData = extra; - } - - entry.ProcessExtraData(true); - if (entry.CompressedSize >= 0) - { - csize = entry.CompressedSize; - } - - if (entry.Size >= 0) - { - size = entry.Size; - } - - if (method == CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))) - { - throw new ZipException("Stored, but compressed != uncompressed"); - } - - // Determine how to handle reading of data if this is attempted. - if (IsEntryCompressionMethodSupported(entry)) - { - internalReader = new ReadDataHandler(InitialRead); - } - else - { - internalReader = new ReadDataHandler(ReadingNotSupported); - } - - return entry; - } - - /// - /// Read data descriptor at the end of compressed data. - /// - private void ReadDataDescriptor() - { - if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature) - { - throw new ZipException("Data descriptor signature not found"); - } - - entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL; - - if (entry.LocalHeaderRequiresZip64) - { - csize = inputBuffer.ReadLeLong(); - size = inputBuffer.ReadLeLong(); - } - else - { - csize = inputBuffer.ReadLeInt(); - size = inputBuffer.ReadLeInt(); - } - entry.CompressedSize = csize; - entry.Size = size; - } - - /// - /// Complete cleanup as the final part of closing. - /// - /// True if the crc value should be tested - private void CompleteCloseEntry(bool testCrc) - { - StopDecrypting(); - - if ((flags & 8) != 0) - { - ReadDataDescriptor(); - } - - size = 0; - - if (testCrc && - ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1)) - { - throw new ZipException("CRC mismatch"); - } - - crc.Reset(); - - if (method == CompressionMethod.Deflated) - { - inf.Reset(); - } - entry = null; - } - - /// - /// Closes the current zip entry and moves to the next one. - /// - /// - /// The stream is closed - /// - /// - /// The Zip stream ends early - /// - public void CloseEntry() - { - if (crc == null) - { - throw new InvalidOperationException("Closed"); - } - - if (entry == null) - { - return; - } - - if (method == CompressionMethod.Deflated) - { - if ((flags & 8) != 0) - { - // We don't know how much we must skip, read until end. - byte[] tmp = new byte[4096]; - - // Read will close this entry - while (Read(tmp, 0, tmp.Length) > 0) - { - } - return; - } - - csize -= inf.TotalIn; - inputBuffer.Available += inf.RemainingInput; - } - - if ((inputBuffer.Available > csize) && (csize >= 0)) - { - inputBuffer.Available = (int)((long)inputBuffer.Available - csize); - } - else - { - csize -= inputBuffer.Available; - inputBuffer.Available = 0; - while (csize != 0) - { - long skipped = Skip(csize); - - if (skipped <= 0) - { - throw new ZipException("Zip archive ends early."); - } - - csize -= skipped; - } - } - - CompleteCloseEntry(false); - } - - /// - /// Returns 1 if there is an entry available - /// Otherwise returns 0. - /// - public override int Available - { - get - { - return entry != null ? 1 : 0; - } - } - - /// - /// Returns the current size that can be read from the current entry if available - /// - /// Thrown if the entry size is not known. - /// Thrown if no entry is currently available. - public override long Length - { - get - { - if (entry != null) - { - if (entry.Size >= 0) - { - return entry.Size; - } - else - { - throw new ZipException("Length not available for the current entry"); - } - } - else - { - throw new InvalidOperationException("No current entry"); - } - } - } - - /// - /// Reads a byte from the current zip entry. - /// - /// - /// The byte or -1 if end of stream is reached. - /// - public override int ReadByte() - { - byte[] b = new byte[1]; - if (Read(b, 0, 1) <= 0) - { - return -1; - } - return b[0] & 0xff; - } - - /// - /// Handle attempts to read by throwing an . - /// - /// The destination array to store data in. - /// The offset at which data read should be stored. - /// The maximum number of bytes to read. - /// Returns the number of bytes actually read. - private int ReadingNotAvailable(byte[] destination, int offset, int count) - { - throw new InvalidOperationException("Unable to read from this stream"); - } - - /// - /// Handle attempts to read from this entry by throwing an exception - /// - private int ReadingNotSupported(byte[] destination, int offset, int count) - { - throw new ZipException("The compression method for this entry is not supported"); - } - - /// - /// Handle attempts to read from this entry by throwing an exception - /// - private int StoredDescriptorEntry(byte[] destination, int offset, int count) => - throw new StreamUnsupportedException( - "The combination of Stored compression method and Descriptor flag is not possible to read using ZipInputStream"); - - - /// - /// Perform the initial read on an entry which may include - /// reading encryption headers and setting up inflation. - /// - /// The destination to fill with data read. - /// The offset to start reading at. - /// The maximum number of bytes to read. - /// The actual number of bytes read. - private int InitialRead(byte[] destination, int offset, int count) - { - var usesDescriptor = (entry.Flags & (int)GeneralBitFlags.Descriptor) != 0; - - // Handle encryption if required. - if (entry.IsCrypted) - { - if (password == null) - { - throw new ZipException("No password set."); - } - - // Generate and set crypto transform... - var managed = new PkzipClassicManaged(); - byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password)); - - inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null); - - byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; - inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize); - - if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) - { - throw new ZipException("Invalid password"); - } - - if (csize >= ZipConstants.CryptoHeaderSize) - { - csize -= ZipConstants.CryptoHeaderSize; - } - else if (!usesDescriptor) - { - throw new ZipException($"Entry compressed size {csize} too small for encryption"); - } - } - else - { - inputBuffer.CryptoTransform = null; - } - - if (csize > 0 || usesDescriptor) - { - if (method == CompressionMethod.Deflated && inputBuffer.Available > 0) - { - inputBuffer.SetInflaterInput(inf); - } - - // It's not possible to know how many bytes to read when using "Stored" compression (unless using encryption) - if (!entry.IsCrypted && method == CompressionMethod.Stored && usesDescriptor) - { - internalReader = StoredDescriptorEntry; - return StoredDescriptorEntry(destination, offset, count); - } - - if (!CanDecompressEntry) - { - internalReader = ReadingNotSupported; - return ReadingNotSupported(destination, offset, count); - } - - internalReader = BodyRead; - return BodyRead(destination, offset, count); - } - - - internalReader = ReadingNotAvailable; - return 0; - } - - /// - /// Read a block of bytes from the stream. - /// - /// The destination for the bytes. - /// The index to start storing data. - /// The number of bytes to attempt to read. - /// Returns the number of bytes read. - /// Zero bytes read means end of stream. - public override int Read(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); - } - - if ((buffer.Length - offset) < count) - { - throw new ArgumentException("Invalid offset/count combination"); - } - - return internalReader(buffer, offset, count); - } - - /// - /// Reads a block of bytes from the current zip entry. - /// - /// - /// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on end of stream. - /// - /// - /// An i/o error occurred. - /// - /// - /// The deflated stream is corrupted. - /// - /// - /// The stream is not open. - /// - private int BodyRead(byte[] buffer, int offset, int count) - { - if (crc == null) - { - throw new InvalidOperationException("Closed"); - } - - if ((entry == null) || (count <= 0)) - { - return 0; - } - - if (offset + count > buffer.Length) - { - throw new ArgumentException("Offset + count exceeds buffer size"); - } - - bool finished = false; - - switch (method) - { - case CompressionMethod.Deflated: - count = base.Read(buffer, offset, count); - if (count <= 0) - { - if (!inf.IsFinished) - { - throw new ZipException("Inflater not finished!"); - } - inputBuffer.Available = inf.RemainingInput; - - // A csize of -1 is from an unpatched local header - if ((flags & 8) == 0 && - (inf.TotalIn != csize && csize != 0xFFFFFFFF && csize != -1 || inf.TotalOut != size)) - { - throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut); - } - inf.Reset(); - finished = true; - } - break; - - case CompressionMethod.Stored: - if ((count > csize) && (csize >= 0)) - { - count = (int)csize; - } - - if (count > 0) - { - count = inputBuffer.ReadClearTextBuffer(buffer, offset, count); - if (count > 0) - { - csize -= count; - size -= count; - } - } - - if (csize == 0) - { - finished = true; - } - else - { - if (count < 0) - { - throw new ZipException("EOF in stored block"); - } - } - break; - } - - if (count > 0) - { - crc.Update(new ArraySegment(buffer, offset, count)); - } - - if (finished) - { - CompleteCloseEntry(true); - } - - return count; - } - - /// - /// Closes the zip input stream - /// - protected override void Dispose(bool disposing) - { - internalReader = new ReadDataHandler(ReadingNotAvailable); - crc = null; - entry = null; - - base.Dispose(disposing); - } - } + #region Instance Fields + + /// + /// Delegate for reading bytes from a stream. + /// + private delegate int ReadDataHandler(byte[] b, int offset, int length); + + /// + /// The current reader this instance. + /// + private ReadDataHandler internalReader; + + private Crc32 crc = new(); + private ZipEntry entry; + + private long size; + private CompressionMethod method; + private int flags; + private string password; + + #endregion Instance Fields + + #region Constructors + + /// + /// Creates a new Zip input stream, for reading a zip archive. + /// + /// The underlying providing data. + public ZipInputStream(Stream baseInputStream) + : base(baseInputStream, new Inflater(true)) + { + internalReader = new ReadDataHandler(ReadingNotAvailable); + } + + /// + /// Creates a new Zip input stream, for reading a zip archive. + /// + /// The underlying providing data. + /// Size of the buffer. + public ZipInputStream(Stream baseInputStream, int bufferSize) + : base(baseInputStream, new Inflater(true), bufferSize) + { + internalReader = new ReadDataHandler(ReadingNotAvailable); + } + + #endregion Constructors + + /// + /// Optional password used for encryption when non-null + /// + /// A password for all encrypted entries in this + public string Password + { + get + { + return password; + } + set + { + password = value; + } + } + + /// + /// Gets a value indicating if there is a current entry and it can be decompressed + /// + /// + /// The entry can only be decompressed if the library supports the zip features required to extract it. + /// See the ZipEntry Version property for more details. + /// + /// Since uses the local headers for extraction, entries with no compression combined with the + /// flag set, cannot be extracted as the end of the entry data cannot be deduced. + /// + public bool CanDecompressEntry + => entry != null + && IsEntryCompressionMethodSupported(entry) + && entry.CanDecompress + && (!entry.HasFlag(GeneralBitFlags.Descriptor) || entry.CompressionMethod != CompressionMethod.Stored || entry.IsCrypted); + + /// + /// Is the compression method for the specified entry supported? + /// + /// + /// Uses entry.CompressionMethodForHeader so that entries of type WinZipAES will be rejected. + /// + /// the entry to check. + /// true if the compression method is supported, false if not. + private static bool IsEntryCompressionMethodSupported(ZipEntry entry) + { + var entryCompressionMethod = entry.CompressionMethodForHeader; + + return entryCompressionMethod is CompressionMethod.Deflated or + CompressionMethod.Stored; + } + + /// + /// Advances to the next entry in the archive + /// + /// + /// The next entry in the archive or null if there are no more entries. + /// + /// + /// If the previous entry is still open CloseEntry is called. + /// + /// + /// Input stream is closed + /// + /// + /// Password is not set, password is invalid, compression method is invalid, + /// version required to extract is not supported + /// + public ZipEntry GetNextEntry() + { + if (crc == null) + { + throw new InvalidOperationException("Closed."); + } + + if (entry != null) + { + CloseEntry(); + } + + var header = inputBuffer.ReadLeInt(); + + if (header is ZipConstants.CentralHeaderSignature or + ZipConstants.EndOfCentralDirectorySignature or + ZipConstants.CentralHeaderDigitalSignature or + ZipConstants.ArchiveExtraDataSignature or + ZipConstants.Zip64CentralFileHeaderSignature) + { + // No more individual entries exist + Dispose(); + return null; + } + + // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found + // Spanning signature is same as descriptor signature and is untested as yet. + if (header is ZipConstants.SpanningTempSignature or ZipConstants.SpanningSignature) + { + header = inputBuffer.ReadLeInt(); + } + + if (header != ZipConstants.LocalHeaderSignature) + { + throw new ZipException("Wrong Local header signature: 0x" + string.Format("{0:X}", header)); + } + + var versionRequiredToExtract = (short)inputBuffer.ReadLeShort(); + + flags = inputBuffer.ReadLeShort(); + method = (CompressionMethod)inputBuffer.ReadLeShort(); + var dostime = (uint)inputBuffer.ReadLeInt(); + var crc2 = inputBuffer.ReadLeInt(); + csize = inputBuffer.ReadLeInt(); + size = inputBuffer.ReadLeInt(); + var nameLen = inputBuffer.ReadLeShort(); + var extraLen = inputBuffer.ReadLeShort(); + + var isCrypted = (flags & 1) == 1; + + var buffer = new byte[nameLen]; + inputBuffer.ReadRawBuffer(buffer); + + var name = ZipStrings.ConvertToStringExt(flags, buffer); + + entry = new ZipEntry(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, method) + { + Flags = flags, + }; + + if ((flags & 8) == 0) + { + entry.Crc = crc2 & 0xFFFFFFFFL; + entry.Size = size & 0xFFFFFFFFL; + entry.CompressedSize = csize & 0xFFFFFFFFL; + + entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff); + } + else + { + // This allows for GNU, WinZip and possibly other archives, the PKZIP spec + // says these values are zero under these circumstances. + if (crc2 != 0) + { + entry.Crc = crc2 & 0xFFFFFFFFL; + } + + if (size != 0) + { + entry.Size = size & 0xFFFFFFFFL; + } + + if (csize != 0) + { + entry.CompressedSize = csize & 0xFFFFFFFFL; + } + + entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff); + } + + entry.DosTime = dostime; + + // If local header requires Zip64 is true then the extended header should contain + // both values. + + // Handle extra data if present. This can set/alter some fields of the entry. + if (extraLen > 0) + { + var extra = new byte[extraLen]; + inputBuffer.ReadRawBuffer(extra); + entry.ExtraData = extra; + } + + entry.ProcessExtraData(true); + if (entry.CompressedSize >= 0) + { + csize = entry.CompressedSize; + } + + if (entry.Size >= 0) + { + size = entry.Size; + } + + if (method == CompressionMethod.Stored && ((!isCrypted && csize != size) || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))) + { + throw new ZipException("Stored, but compressed != uncompressed"); + } + + // Determine how to handle reading of data if this is attempted. + internalReader = IsEntryCompressionMethodSupported(entry) ? new ReadDataHandler(InitialRead) : new ReadDataHandler(ReadingNotSupported); + + return entry; + } + + /// + /// Read data descriptor at the end of compressed data. + /// + private void ReadDataDescriptor() + { + if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature) + { + throw new ZipException("Data descriptor signature not found"); + } + + entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL; + + if (entry.LocalHeaderRequiresZip64) + { + csize = inputBuffer.ReadLeLong(); + size = inputBuffer.ReadLeLong(); + } + else + { + csize = inputBuffer.ReadLeInt(); + size = inputBuffer.ReadLeInt(); + } + entry.CompressedSize = csize; + entry.Size = size; + } + + /// + /// Complete cleanup as the final part of closing. + /// + /// True if the crc value should be tested + private void CompleteCloseEntry(bool testCrc) + { + StopDecrypting(); + + if ((flags & 8) != 0) + { + ReadDataDescriptor(); + } + + size = 0; + + if (testCrc && + ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1)) + { + throw new ZipException("CRC mismatch"); + } + + crc.Reset(); + + if (method == CompressionMethod.Deflated) + { + inf.Reset(); + } + entry = null; + } + + /// + /// Closes the current zip entry and moves to the next one. + /// + /// + /// The stream is closed + /// + /// + /// The Zip stream ends early + /// + public void CloseEntry() + { + if (crc == null) + { + throw new InvalidOperationException("Closed"); + } + + if (entry == null) + { + return; + } + + if (method == CompressionMethod.Deflated) + { + if ((flags & 8) != 0) + { + // We don't know how much we must skip, read until end. + var tmp = new byte[4096]; + + // Read will close this entry + while (Read(tmp, 0, tmp.Length) > 0) + { + } + return; + } + + csize -= inf.TotalIn; + inputBuffer.Available += inf.RemainingInput; + } + + if ((inputBuffer.Available > csize) && (csize >= 0)) + { + inputBuffer.Available = (int)(inputBuffer.Available - csize); + } + else + { + csize -= inputBuffer.Available; + inputBuffer.Available = 0; + while (csize != 0) + { + var skipped = Skip(csize); + + if (skipped <= 0) + { + throw new ZipException("Zip archive ends early."); + } + + csize -= skipped; + } + } + + CompleteCloseEntry(false); + } + + /// + /// Returns 1 if there is an entry available + /// Otherwise returns 0. + /// + public override int Available + { + get + { + return entry != null ? 1 : 0; + } + } + + /// + /// Returns the current size that can be read from the current entry if available + /// + /// Thrown if the entry size is not known. + /// Thrown if no entry is currently available. + public override long Length + { + get + { + if (entry != null) + { + return entry.Size >= 0 ? entry.Size : throw new ZipException("Length not available for the current entry"); + } + else + { + throw new InvalidOperationException("No current entry"); + } + } + } + + /// + /// Reads a byte from the current zip entry. + /// + /// + /// The byte or -1 if end of stream is reached. + /// + public override int ReadByte() + { + var b = new byte[1]; + return Read(b, 0, 1) <= 0 ? -1 : b[0] & 0xff; + } + + /// + /// Handle attempts to read by throwing an . + /// + /// The destination array to store data in. + /// The offset at which data read should be stored. + /// The maximum number of bytes to read. + /// Returns the number of bytes actually read. + private int ReadingNotAvailable(byte[] destination, int offset, int count) + { + throw new InvalidOperationException("Unable to read from this stream"); + } + + /// + /// Handle attempts to read from this entry by throwing an exception + /// + private int ReadingNotSupported(byte[] destination, int offset, int count) + { + throw new ZipException("The compression method for this entry is not supported"); + } + + /// + /// Handle attempts to read from this entry by throwing an exception + /// + private int StoredDescriptorEntry(byte[] destination, int offset, int count) => + throw new StreamUnsupportedException( + "The combination of Stored compression method and Descriptor flag is not possible to read using ZipInputStream"); + + + /// + /// Perform the initial read on an entry which may include + /// reading encryption headers and setting up inflation. + /// + /// The destination to fill with data read. + /// The offset to start reading at. + /// The maximum number of bytes to read. + /// The actual number of bytes read. + private int InitialRead(byte[] destination, int offset, int count) + { + var usesDescriptor = (entry.Flags & (int)GeneralBitFlags.Descriptor) != 0; + + // Handle encryption if required. + if (entry.IsCrypted) + { + if (password == null) + { + throw new ZipException("No password set."); + } + + // Generate and set crypto transform... + var managed = new PkzipClassicManaged(); + var key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password)); + + inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null); + + var cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; + inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize); + + if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) + { + throw new ZipException("Invalid password"); + } + + if (csize >= ZipConstants.CryptoHeaderSize) + { + csize -= ZipConstants.CryptoHeaderSize; + } + else if (!usesDescriptor) + { + throw new ZipException($"Entry compressed size {csize} too small for encryption"); + } + } + else + { + inputBuffer.CryptoTransform = null; + } + + if (csize > 0 || usesDescriptor) + { + if (method == CompressionMethod.Deflated && inputBuffer.Available > 0) + { + inputBuffer.SetInflaterInput(inf); + } + + // It's not possible to know how many bytes to read when using "Stored" compression (unless using encryption) + if (!entry.IsCrypted && method == CompressionMethod.Stored && usesDescriptor) + { + internalReader = StoredDescriptorEntry; + return StoredDescriptorEntry(destination, offset, count); + } + + if (!CanDecompressEntry) + { + internalReader = ReadingNotSupported; + return ReadingNotSupported(destination, offset, count); + } + + internalReader = BodyRead; + return BodyRead(destination, offset, count); + } + + + internalReader = ReadingNotAvailable; + return 0; + } + + /// + /// Read a block of bytes from the stream. + /// + /// The destination for the bytes. + /// The index to start storing data. + /// The number of bytes to attempt to read. + /// Returns the number of bytes read. + /// Zero bytes read means end of stream. + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); + } + + return (buffer.Length - offset) < count + ? throw new ArgumentException("Invalid offset/count combination") + : internalReader(buffer, offset, count); + } + + /// + /// Reads a block of bytes from the current zip entry. + /// + /// + /// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on end of stream. + /// + /// + /// An i/o error occurred. + /// + /// + /// The deflated stream is corrupted. + /// + /// + /// The stream is not open. + /// + private int BodyRead(byte[] buffer, int offset, int count) + { + if (crc == null) + { + throw new InvalidOperationException("Closed"); + } + + if ((entry == null) || (count <= 0)) + { + return 0; + } + + if (offset + count > buffer.Length) + { + throw new ArgumentException("Offset + count exceeds buffer size"); + } + + var finished = false; + + switch (method) + { + case CompressionMethod.Deflated: + count = base.Read(buffer, offset, count); + if (count <= 0) + { + if (!inf.IsFinished) + { + throw new ZipException("Inflater not finished!"); + } + inputBuffer.Available = inf.RemainingInput; + + // A csize of -1 is from an unpatched local header + if ((flags & 8) == 0 && + ((inf.TotalIn != csize && csize != 0xFFFFFFFF && csize != -1) || inf.TotalOut != size)) + { + throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut); + } + inf.Reset(); + finished = true; + } + break; + + case CompressionMethod.Stored: + if ((count > csize) && (csize >= 0)) + { + count = (int)csize; + } + + if (count > 0) + { + count = inputBuffer.ReadClearTextBuffer(buffer, offset, count); + if (count > 0) + { + csize -= count; + size -= count; + } + } + + if (csize == 0) + { + finished = true; + } + else + { + if (count < 0) + { + throw new ZipException("EOF in stored block"); + } + } + break; + } + + if (count > 0) + { + crc.Update(new ArraySegment(buffer, offset, count)); + } + + if (finished) + { + CompleteCloseEntry(true); + } + + return count; + } + + /// + /// Closes the zip input stream + /// + protected override void Dispose(bool disposing) + { + internalReader = new ReadDataHandler(ReadingNotAvailable); + crc = null; + entry = null; + + base.Dispose(disposing); + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs index 44adc9237..7761c783a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs @@ -3,311 +3,292 @@ using System.IO; using System.Text; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// ZipNameTransform transforms names as per the Zip file naming convention. +/// +/// The use of absolute names is supported although its use is not valid +/// according to Zip naming conventions, and should not be used if maximum compatability is desired. +public class ZipNameTransform : INameTransform { - /// - /// ZipNameTransform transforms names as per the Zip file naming convention. - /// - /// The use of absolute names is supported although its use is not valid - /// according to Zip naming conventions, and should not be used if maximum compatability is desired. - public class ZipNameTransform : INameTransform - { - #region Constructors - - /// - /// Initialize a new instance of - /// - public ZipNameTransform() - { - } - - /// - /// Initialize a new instance of - /// - /// The string to trim from the front of paths if found. - public ZipNameTransform(string trimPrefix) - { - TrimPrefix = trimPrefix; - } - - #endregion Constructors - - /// - /// Static constructor. - /// - static ZipNameTransform() - { - char[] invalidPathChars; - invalidPathChars = Path.GetInvalidPathChars(); - int howMany = invalidPathChars.Length + 2; - - InvalidEntryCharsRelaxed = new char[howMany]; - Array.Copy(invalidPathChars, 0, InvalidEntryCharsRelaxed, 0, invalidPathChars.Length); - InvalidEntryCharsRelaxed[howMany - 1] = '*'; - InvalidEntryCharsRelaxed[howMany - 2] = '?'; - - howMany = invalidPathChars.Length + 4; - InvalidEntryChars = new char[howMany]; - Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length); - InvalidEntryChars[howMany - 1] = ':'; - InvalidEntryChars[howMany - 2] = '\\'; - InvalidEntryChars[howMany - 3] = '*'; - InvalidEntryChars[howMany - 4] = '?'; - } - - /// - /// Transform a windows directory name according to the Zip file naming conventions. - /// - /// The directory name to transform. - /// The transformed name. - public string TransformDirectory(string name) - { - name = TransformFile(name); - if (name.Length > 0) - { - if (!name.EndsWith("/", StringComparison.Ordinal)) - { - name += "/"; - } - } - else - { - throw new ZipException("Cannot have an empty directory name"); - } - return name; - } - - /// - /// Transform a windows file name according to the Zip file naming conventions. - /// - /// The file name to transform. - /// The transformed name. - public string TransformFile(string name) - { - if (name != null) - { - string lowerName = name.ToLower(); - if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0)) - { - name = name.Substring(trimPrefix_.Length); - } - - name = name.Replace(@"\", "/"); - name = PathUtils.DropPathRoot(name); - - // Drop any leading and trailing slashes. - name = name.Trim('/'); - - // Convert consecutive // characters to / - int index = name.IndexOf("//", StringComparison.Ordinal); - while (index >= 0) - { - name = name.Remove(index, 1); - index = name.IndexOf("//", StringComparison.Ordinal); - } - - name = MakeValidName(name, '_'); - } - else - { - name = string.Empty; - } - return name; - } - - /// - /// Get/set the path prefix to be trimmed from paths if present. - /// - /// The prefix is trimmed before any conversion from - /// a windows path is done. - public string TrimPrefix - { - get { return trimPrefix_; } - set - { - trimPrefix_ = value; - if (trimPrefix_ != null) - { - trimPrefix_ = trimPrefix_.ToLower(); - } - } - } - - /// - /// Force a name to be valid by replacing invalid characters with a fixed value - /// - /// The name to force valid - /// The replacement character to use. - /// Returns a valid name - private static string MakeValidName(string name, char replacement) - { - int index = name.IndexOfAny(InvalidEntryChars); - if (index >= 0) - { - var builder = new StringBuilder(name); - - while (index >= 0) - { - builder[index] = replacement; - - if (index >= name.Length) - { - index = -1; - } - else - { - index = name.IndexOfAny(InvalidEntryChars, index + 1); - } - } - name = builder.ToString(); - } - - if (name.Length > 0xffff) - { - throw new PathTooLongException(); - } - - return name; - } - - /// - /// Test a name to see if it is a valid name for a zip entry. - /// - /// The name to test. - /// If true checking is relaxed about windows file names and absolute paths. - /// Returns true if the name is a valid zip name; false otherwise. - /// Zip path names are actually in Unix format, and should only contain relative paths. - /// This means that any path stored should not contain a drive or - /// device letter, or a leading slash. All slashes should forward slashes '/'. - /// An empty name is valid for a file where the input comes from standard input. - /// A null name is not considered valid. - /// - public static bool IsValidName(string name, bool relaxed) - { - bool result = (name != null); - - if (result) - { - if (relaxed) - { - result = name.IndexOfAny(InvalidEntryCharsRelaxed) < 0; - } - else - { - result = - (name.IndexOfAny(InvalidEntryChars) < 0) && - (name.IndexOf('/') != 0); - } - } - - return result; - } - - /// - /// Test a name to see if it is a valid name for a zip entry. - /// - /// The name to test. - /// Returns true if the name is a valid zip name; false otherwise. - /// Zip path names are actually in unix format, - /// and should only contain relative paths if a path is present. - /// This means that the path stored should not contain a drive or - /// device letter, or a leading slash. All slashes should forward slashes '/'. - /// An empty name is valid where the input comes from standard input. - /// A null name is not considered valid. - /// - public static bool IsValidName(string name) - { - bool result = - (name != null) && - (name.IndexOfAny(InvalidEntryChars) < 0) && - (name.IndexOf('/') != 0) - ; - return result; - } - - #region Instance Fields - - private string trimPrefix_; - - #endregion Instance Fields - - #region Class Fields - - private static readonly char[] InvalidEntryChars; - private static readonly char[] InvalidEntryCharsRelaxed; - - #endregion Class Fields - } - - /// - /// An implementation of INameTransform that transforms entry paths as per the Zip file naming convention. - /// Strips path roots and puts directory separators in the correct format ('/') - /// - public class PathTransformer : INameTransform - { - /// - /// Initialize a new instance of - /// - public PathTransformer() - { - } - - /// - /// Transform a windows directory name according to the Zip file naming conventions. - /// - /// The directory name to transform. - /// The transformed name. - public string TransformDirectory(string name) - { - name = TransformFile(name); - - if (name.Length > 0) - { - if (!name.EndsWith("/", StringComparison.Ordinal)) - { - name += "/"; - } - } - else - { - throw new ZipException("Cannot have an empty directory name"); - } - - return name; - } - - /// - /// Transform a windows file name according to the Zip file naming conventions. - /// - /// The file name to transform. - /// The transformed name. - public string TransformFile(string name) - { - if (name != null) - { - // Put separators in the expected format. - name = name.Replace(@"\", "/"); - - // Remove the path root. - name = PathUtils.DropPathRoot(name); - - // Drop any leading and trailing slashes. - name = name.Trim('/'); - - // Convert consecutive // characters to / - int index = name.IndexOf("//", StringComparison.Ordinal); - while (index >= 0) - { - name = name.Remove(index, 1); - index = name.IndexOf("//", StringComparison.Ordinal); - } - } - else - { - name = string.Empty; - } - - return name; - } - } + #region Constructors + + /// + /// Initialize a new instance of + /// + public ZipNameTransform() + { + } + + /// + /// Initialize a new instance of + /// + /// The string to trim from the front of paths if found. + public ZipNameTransform(string trimPrefix) + { + TrimPrefix = trimPrefix; + } + + #endregion Constructors + + /// + /// Static constructor. + /// + static ZipNameTransform() + { + char[] invalidPathChars; + invalidPathChars = Path.GetInvalidPathChars(); + var howMany = invalidPathChars.Length + 2; + + InvalidEntryCharsRelaxed = new char[howMany]; + Array.Copy(invalidPathChars, 0, InvalidEntryCharsRelaxed, 0, invalidPathChars.Length); + InvalidEntryCharsRelaxed[howMany - 1] = '*'; + InvalidEntryCharsRelaxed[howMany - 2] = '?'; + + howMany = invalidPathChars.Length + 4; + InvalidEntryChars = new char[howMany]; + Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length); + InvalidEntryChars[howMany - 1] = ':'; + InvalidEntryChars[howMany - 2] = '\\'; + InvalidEntryChars[howMany - 3] = '*'; + InvalidEntryChars[howMany - 4] = '?'; + } + + /// + /// Transform a windows directory name according to the Zip file naming conventions. + /// + /// The directory name to transform. + /// The transformed name. + public string TransformDirectory(string name) + { + name = TransformFile(name); + if (name.Length > 0) + { + if (!name.EndsWith("/", StringComparison.Ordinal)) + { + name += "/"; + } + } + else + { + throw new ZipException("Cannot have an empty directory name"); + } + return name; + } + + /// + /// Transform a windows file name according to the Zip file naming conventions. + /// + /// The file name to transform. + /// The transformed name. + public string TransformFile(string name) + { + if (name != null) + { + var lowerName = name.ToLower(); + if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0)) + { + name = name[trimPrefix_.Length..]; + } + + name = name.Replace(@"\", "/"); + name = PathUtils.DropPathRoot(name); + + // Drop any leading and trailing slashes. + name = name.Trim('/'); + + // Convert consecutive // characters to / + var index = name.IndexOf("//", StringComparison.Ordinal); + while (index >= 0) + { + name = name.Remove(index, 1); + index = name.IndexOf("//", StringComparison.Ordinal); + } + + name = MakeValidName(name, '_'); + } + else + { + name = string.Empty; + } + return name; + } + + /// + /// Get/set the path prefix to be trimmed from paths if present. + /// + /// The prefix is trimmed before any conversion from + /// a windows path is done. + public string TrimPrefix + { + get { return trimPrefix_; } + set + { + trimPrefix_ = value; + if (trimPrefix_ != null) + { + trimPrefix_ = trimPrefix_.ToLower(); + } + } + } + + /// + /// Force a name to be valid by replacing invalid characters with a fixed value + /// + /// The name to force valid + /// The replacement character to use. + /// Returns a valid name + private static string MakeValidName(string name, char replacement) + { + var index = name.IndexOfAny(InvalidEntryChars); + if (index >= 0) + { + var builder = new StringBuilder(name); + + while (index >= 0) + { + builder[index] = replacement; + + index = index >= name.Length ? -1 : name.IndexOfAny(InvalidEntryChars, index + 1); + } + name = builder.ToString(); + } + + return name.Length > 0xffff ? throw new PathTooLongException() : name; + } + + /// + /// Test a name to see if it is a valid name for a zip entry. + /// + /// The name to test. + /// If true checking is relaxed about windows file names and absolute paths. + /// Returns true if the name is a valid zip name; false otherwise. + /// Zip path names are actually in Unix format, and should only contain relative paths. + /// This means that any path stored should not contain a drive or + /// device letter, or a leading slash. All slashes should forward slashes '/'. + /// An empty name is valid for a file where the input comes from standard input. + /// A null name is not considered valid. + /// + public static bool IsValidName(string name, bool relaxed) + { + var result = name != null; + + if (result) + { + result = relaxed + ? name.IndexOfAny(InvalidEntryCharsRelaxed) < 0 + : (name.IndexOfAny(InvalidEntryChars) < 0) && + (name.IndexOf('/') != 0); + } + + return result; + } + + /// + /// Test a name to see if it is a valid name for a zip entry. + /// + /// The name to test. + /// Returns true if the name is a valid zip name; false otherwise. + /// Zip path names are actually in unix format, + /// and should only contain relative paths if a path is present. + /// This means that the path stored should not contain a drive or + /// device letter, or a leading slash. All slashes should forward slashes '/'. + /// An empty name is valid where the input comes from standard input. + /// A null name is not considered valid. + /// + public static bool IsValidName(string name) + { + var result = + (name != null) && + (name.IndexOfAny(InvalidEntryChars) < 0) && + (name.IndexOf('/') != 0) + ; + return result; + } + + #region Instance Fields + + private string trimPrefix_; + + #endregion Instance Fields + + #region Class Fields + + private static readonly char[] InvalidEntryChars; + private static readonly char[] InvalidEntryCharsRelaxed; + + #endregion Class Fields +} + +/// +/// An implementation of INameTransform that transforms entry paths as per the Zip file naming convention. +/// Strips path roots and puts directory separators in the correct format ('/') +/// +public class PathTransformer : INameTransform +{ + /// + /// Initialize a new instance of + /// + public PathTransformer() + { + } + + /// + /// Transform a windows directory name according to the Zip file naming conventions. + /// + /// The directory name to transform. + /// The transformed name. + public string TransformDirectory(string name) + { + name = TransformFile(name); + + if (name.Length > 0) + { + if (!name.EndsWith("/", StringComparison.Ordinal)) + { + name += "/"; + } + } + else + { + throw new ZipException("Cannot have an empty directory name"); + } + + return name; + } + + /// + /// Transform a windows file name according to the Zip file naming conventions. + /// + /// The file name to transform. + /// The transformed name. + public string TransformFile(string name) + { + if (name != null) + { + // Put separators in the expected format. + name = name.Replace(@"\", "/"); + + // Remove the path root. + name = PathUtils.DropPathRoot(name); + + // Drop any leading and trailing slashes. + name = name.Trim('/'); + + // Convert consecutive // characters to / + var index = name.IndexOf("//", StringComparison.Ordinal); + while (index >= 0) + { + name = name.Remove(index, 1); + index = name.IndexOf("//", StringComparison.Ordinal); + } + } + else + { + name = string.Empty; + } + + return name; + } } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs index 6b068d89c..7852e502a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs @@ -8,1070 +8,1055 @@ using System.IO; using System.Security.Cryptography; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// This is a DeflaterOutputStream that writes the files into a zip +/// archive one after another. It has a special method to start a new +/// zip entry. The zip entries contains information about the file name +/// size, compressed size, CRC, etc. +/// +/// It includes support for Stored and Deflated entries. +/// This class is not thread safe. +///
+///
Author of the original java version : Jochen Hoenicke +///
+/// This sample shows how to create a zip file +/// +/// using System; +/// using System.IO; +/// +/// using MelonLoader.ICSharpCode.SharpZipLib.Core; +/// using MelonLoader.ICSharpCode.SharpZipLib.Zip; +/// +/// class MainClass +/// { +/// public static void Main(string[] args) +/// { +/// string[] filenames = Directory.GetFiles(args[0]); +/// byte[] buffer = new byte[4096]; +/// +/// using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) { +/// +/// s.SetLevel(9); // 0 - store only to 9 - means best compression +/// +/// foreach (string file in filenames) { +/// ZipEntry entry = new ZipEntry(file); +/// s.PutNextEntry(entry); +/// +/// using (FileStream fs = File.OpenRead(file)) { +/// StreamUtils.Copy(fs, s, buffer); +/// } +/// } +/// } +/// } +/// } +/// +/// +public class ZipOutputStream : DeflaterOutputStream { - /// - /// This is a DeflaterOutputStream that writes the files into a zip - /// archive one after another. It has a special method to start a new - /// zip entry. The zip entries contains information about the file name - /// size, compressed size, CRC, etc. - /// - /// It includes support for Stored and Deflated entries. - /// This class is not thread safe. - ///
- ///
Author of the original java version : Jochen Hoenicke - ///
- /// This sample shows how to create a zip file - /// - /// using System; - /// using System.IO; - /// - /// using MelonLoader.ICSharpCode.SharpZipLib.Core; - /// using MelonLoader.ICSharpCode.SharpZipLib.Zip; - /// - /// class MainClass - /// { - /// public static void Main(string[] args) - /// { - /// string[] filenames = Directory.GetFiles(args[0]); - /// byte[] buffer = new byte[4096]; - /// - /// using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) { - /// - /// s.SetLevel(9); // 0 - store only to 9 - means best compression - /// - /// foreach (string file in filenames) { - /// ZipEntry entry = new ZipEntry(file); - /// s.PutNextEntry(entry); - /// - /// using (FileStream fs = File.OpenRead(file)) { - /// StreamUtils.Copy(fs, s, buffer); - /// } - /// } - /// } - /// } - /// } - /// - /// - public class ZipOutputStream : DeflaterOutputStream - { - #region Constructors - - /// - /// Creates a new Zip output stream, writing a zip archive. - /// - /// - /// The output stream to which the archive contents are written. - /// - public ZipOutputStream(Stream baseOutputStream) - : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)) - { - } - - /// - /// Creates a new Zip output stream, writing a zip archive. - /// - /// The output stream to which the archive contents are written. - /// Size of the buffer to use. - public ZipOutputStream(Stream baseOutputStream, int bufferSize) - : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize) - { - } - - #endregion Constructors - - /// - /// Gets a flag value of true if the central header has been added for this archive; false if it has not been added. - /// - /// No further entries can be added once this has been done. - public bool IsFinished - { - get - { - return entries == null; - } - } - - /// - /// Set the zip file comment. - /// - /// - /// The comment text for the entire archive. - /// - /// - /// The converted comment is longer than 0xffff bytes. - /// - public void SetComment(string comment) - { - // TODO: Its not yet clear how to handle unicode comments here. - byte[] commentBytes = ZipStrings.ConvertToArray(comment); - if (commentBytes.Length > 0xffff) - { - throw new ArgumentOutOfRangeException(nameof(comment)); - } - zipComment = commentBytes; - } - - /// - /// Sets the compression level. The new level will be activated - /// immediately. - /// - /// The new compression level (1 to 9). - /// - /// Level specified is not supported. - /// - /// - public void SetLevel(int level) - { - deflater_.SetLevel(level); - defaultCompressionLevel = level; - } - - /// - /// Get the current deflater compression level - /// - /// The current compression level - public int GetLevel() - { - return deflater_.GetLevel(); - } - - /// - /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. - /// - /// Older archivers may not understand Zip64 extensions. - /// If backwards compatability is an issue be careful when adding entries to an archive. - /// Setting this property to off is workable but less desirable as in those circumstances adding a file - /// larger then 4GB will fail. - public UseZip64 UseZip64 - { - get { return useZip64_; } - set { useZip64_ = value; } - } - - /// - /// Used for transforming the names of entries added by . - /// Defaults to , set to null to disable transforms and use names as supplied. - /// - public INameTransform NameTransform { get; set; } = new PathTransformer(); - - /// - /// Get/set the password used for encryption. - /// - /// When set to null or if the password is empty no encryption is performed - public string Password - { - get - { - return password; - } - set - { - if ((value != null) && (value.Length == 0)) - { - password = null; - } - else - { - password = value; - } - } - } - - /// - /// Write an unsigned short in little endian byte order. - /// - private void WriteLeShort(int value) - { - unchecked - { - baseOutputStream_.WriteByte((byte)(value & 0xff)); - baseOutputStream_.WriteByte((byte)((value >> 8) & 0xff)); - } - } - - /// - /// Write an int in little endian byte order. - /// - private void WriteLeInt(int value) - { - unchecked - { - WriteLeShort(value); - WriteLeShort(value >> 16); - } - } - - /// - /// Write an int in little endian byte order. - /// - private void WriteLeLong(long value) - { - unchecked - { - WriteLeInt((int)value); - WriteLeInt((int)(value >> 32)); - } - } - - // Apply any configured transforms/cleaning to the name of the supplied entry. - private void TransformEntryName(ZipEntry entry) - { - if (this.NameTransform != null) - { - if (entry.IsDirectory) - { - entry.Name = this.NameTransform.TransformDirectory(entry.Name); - } - else - { - entry.Name = this.NameTransform.TransformFile(entry.Name); - } - } - } - - /// - /// Starts a new Zip entry. It automatically closes the previous - /// entry if present. - /// All entry elements bar name are optional, but must be correct if present. - /// If the compression method is stored and the output is not patchable - /// the compression for that entry is automatically changed to deflate level 0 - /// - /// - /// the entry. - /// - /// - /// if entry passed is null. - /// - /// - /// if an I/O error occured. - /// - /// - /// if stream was finished - /// - /// - /// Too many entries in the Zip file
- /// Entry name is too long
- /// Finish has already been called
- ///
- /// - /// The Compression method specified for the entry is unsupported. - /// - public void PutNextEntry(ZipEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - if (entries == null) - { - throw new InvalidOperationException("ZipOutputStream was finished"); - } - - if (curEntry != null) - { - CloseEntry(); - } - - if (entries.Count == int.MaxValue) - { - throw new ZipException("Too many entries for Zip file"); - } - - CompressionMethod method = entry.CompressionMethod; - - // Check that the compression is one that we support - if (method != CompressionMethod.Deflated && method != CompressionMethod.Stored) - { - throw new NotImplementedException("Compression method not supported"); - } - - // A password must have been set in order to add AES encrypted entries - if (entry.AESKeySize > 0 && string.IsNullOrEmpty(this.Password)) - { - throw new InvalidOperationException("The Password property must be set before AES encrypted entries can be added"); - } - - int compressionLevel = defaultCompressionLevel; - - // Clear flags that the library manages internally - entry.Flags &= (int)GeneralBitFlags.UnicodeText; - patchEntryHeader = false; - - bool headerInfoAvailable; - - // No need to compress - definitely no data. - if (entry.Size == 0) - { - entry.CompressedSize = entry.Size; - entry.Crc = 0; - method = CompressionMethod.Stored; - headerInfoAvailable = true; - } - else - { - headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0; - - // Switch to deflation if storing isnt possible. - if (method == CompressionMethod.Stored) - { - if (!headerInfoAvailable) - { - if (!CanPatchEntries) - { - // Can't patch entries so storing is not possible. - method = CompressionMethod.Deflated; - compressionLevel = 0; - } - } - else // entry.size must be > 0 - { - entry.CompressedSize = entry.Size; - headerInfoAvailable = entry.HasCrc; - } - } - } - - if (headerInfoAvailable == false) - { - if (CanPatchEntries == false) - { - // Only way to record size and compressed size is to append a data descriptor - // after compressed data. - - // Stored entries of this form have already been converted to deflating. - entry.Flags |= 8; - } - else - { - patchEntryHeader = true; - } - } - - if (Password != null) - { - entry.IsCrypted = true; - if (entry.Crc < 0) - { - // Need to append a data descriptor as the crc isnt available for use - // with encryption, the date is used instead. Setting the flag - // indicates this to the decompressor. - entry.Flags |= 8; - } - } - - entry.Offset = offset; - entry.CompressionMethod = (CompressionMethod)method; - - curMethod = method; - sizePatchPos = -1; - - if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic))) - { - entry.ForceZip64(); - } - - // Write the local file header - WriteLeInt(ZipConstants.LocalHeaderSignature); - - WriteLeShort(entry.Version); - WriteLeShort(entry.Flags); - WriteLeShort((byte)entry.CompressionMethodForHeader); - WriteLeInt((int)entry.DosTime); - - // TODO: Refactor header writing. Its done in several places. - if (headerInfoAvailable) - { - WriteLeInt((int)entry.Crc); - if (entry.LocalHeaderRequiresZip64) - { - WriteLeInt(-1); - WriteLeInt(-1); - } - else - { - WriteLeInt((int)entry.CompressedSize + entry.EncryptionOverheadSize); - WriteLeInt((int)entry.Size); - } - } - else - { - if (patchEntryHeader) - { - crcPatchPos = baseOutputStream_.Position; - } - WriteLeInt(0); // Crc - - if (patchEntryHeader) - { - sizePatchPos = baseOutputStream_.Position; - } - - // For local header both sizes appear in Zip64 Extended Information - if (entry.LocalHeaderRequiresZip64 || patchEntryHeader) - { - WriteLeInt(-1); - WriteLeInt(-1); - } - else - { - WriteLeInt(0); // Compressed size - WriteLeInt(0); // Uncompressed size - } - } - - // Apply any required transforms to the entry name, and then convert to byte array format. - TransformEntryName(entry); - byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); - - if (name.Length > 0xFFFF) - { - throw new ZipException("Entry name too long."); - } - - var ed = new ZipExtraData(entry.ExtraData); - - if (entry.LocalHeaderRequiresZip64) - { - ed.StartNewEntry(); - if (headerInfoAvailable) - { - ed.AddLeLong(entry.Size); - ed.AddLeLong(entry.CompressedSize + entry.EncryptionOverheadSize); - } - else - { - ed.AddLeLong(-1); - ed.AddLeLong(-1); - } - ed.AddNewEntry(1); - - if (!ed.Find(1)) - { - throw new ZipException("Internal error cant find extra data"); - } - - if (patchEntryHeader) - { - sizePatchPos = ed.CurrentReadIndex; - } - } - else - { - ed.Delete(1); - } - - if (entry.AESKeySize > 0) - { - AddExtraDataAES(entry, ed); - } - byte[] extra = ed.GetEntryData(); - - WriteLeShort(name.Length); - WriteLeShort(extra.Length); - - if (name.Length > 0) - { - baseOutputStream_.Write(name, 0, name.Length); - } - - if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) - { - sizePatchPos += baseOutputStream_.Position; - } - - if (extra.Length > 0) - { - baseOutputStream_.Write(extra, 0, extra.Length); - } - - offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length; - // Fix offsetOfCentraldir for AES - if (entry.AESKeySize > 0) - offset += entry.AESOverheadSize; - - // Activate the entry. - curEntry = entry; - crc.Reset(); - if (method == CompressionMethod.Deflated) - { - deflater_.Reset(); - deflater_.SetLevel(compressionLevel); - } - size = 0; - - if (entry.IsCrypted) - { - if (entry.AESKeySize > 0) - { - WriteAESHeader(entry); - } - else - { - if (entry.Crc < 0) - { // so testing Zip will says its ok - WriteEncryptionHeader(entry.DosTime << 16); - } - else - { - WriteEncryptionHeader(entry.Crc); - } - } - } - } - - /// - /// Closes the current entry, updating header and footer information as required - /// - /// - /// Invalid entry field values. - /// - /// - /// An I/O error occurs. - /// - /// - /// No entry is active. - /// - public void CloseEntry() - { - if (curEntry == null) - { - throw new InvalidOperationException("No open entry"); - } - - long csize = size; - - // First finish the deflater, if appropriate - if (curMethod == CompressionMethod.Deflated) - { - if (size >= 0) - { - base.Finish(); - csize = deflater_.TotalOut; - } - else - { - deflater_.Reset(); - } - } - else if (curMethod == CompressionMethod.Stored) - { - // This is done by Finish() for Deflated entries, but we need to do it - // ourselves for Stored ones - base.GetAuthCodeIfAES(); - } - - // Write the AES Authentication Code (a hash of the compressed and encrypted data) - if (curEntry.AESKeySize > 0) - { - baseOutputStream_.Write(AESAuthCode, 0, 10); - // Always use 0 as CRC for AE-2 format - curEntry.Crc = 0; - } - else - { - if (curEntry.Crc < 0) - { - curEntry.Crc = crc.Value; - } - else if (curEntry.Crc != crc.Value) - { - throw new ZipException($"crc was {crc.Value}, but {curEntry.Crc} was expected"); - } - } - - if (curEntry.Size < 0) - { - curEntry.Size = size; - } - else if (curEntry.Size != size) - { - throw new ZipException($"size was {size}, but {curEntry.Size} was expected"); - } - - if (curEntry.CompressedSize < 0) - { - curEntry.CompressedSize = csize; - } - else if (curEntry.CompressedSize != csize) - { - throw new ZipException($"compressed size was {csize}, but {curEntry.CompressedSize} expected"); - } - - offset += csize; - - if (curEntry.IsCrypted) - { - curEntry.CompressedSize += curEntry.EncryptionOverheadSize; - } - - // Patch the header if possible - if (patchEntryHeader) - { - patchEntryHeader = false; - - long curPos = baseOutputStream_.Position; - baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin); - WriteLeInt((int)curEntry.Crc); - - if (curEntry.LocalHeaderRequiresZip64) - { - if (sizePatchPos == -1) - { - throw new ZipException("Entry requires zip64 but this has been turned off"); - } - - baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin); - WriteLeLong(curEntry.Size); - WriteLeLong(curEntry.CompressedSize); - } - else - { - WriteLeInt((int)curEntry.CompressedSize); - WriteLeInt((int)curEntry.Size); - } - baseOutputStream_.Seek(curPos, SeekOrigin.Begin); - } - - // Add data descriptor if flagged as required - if ((curEntry.Flags & 8) != 0) - { - WriteLeInt(ZipConstants.DataDescriptorSignature); - WriteLeInt(unchecked((int)curEntry.Crc)); - - if (curEntry.LocalHeaderRequiresZip64) - { - WriteLeLong(curEntry.CompressedSize); - WriteLeLong(curEntry.Size); - offset += ZipConstants.Zip64DataDescriptorSize; - } - else - { - WriteLeInt((int)curEntry.CompressedSize); - WriteLeInt((int)curEntry.Size); - offset += ZipConstants.DataDescriptorSize; - } - } - - entries.Add(curEntry); - curEntry = null; - } - - /// - /// Initializes encryption keys based on given . - /// - /// The password. - private void InitializePassword(string password) - { - var pkManaged = new PkzipClassicManaged(); - byte[] key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password)); - cryptoTransform_ = pkManaged.CreateEncryptor(key, null); - } - - /// - /// Initializes encryption keys based on given password. - /// - private void InitializeAESPassword(ZipEntry entry, string rawPassword, - out byte[] salt, out byte[] pwdVerifier) - { - salt = new byte[entry.AESSaltLen]; - - // Salt needs to be cryptographically random, and unique per file - _aesRnd.GetBytes(salt); - - int blockSize = entry.AESKeySize / 8; // bits to bytes - - cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true); - pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier; - } - - private void WriteEncryptionHeader(long crcValue) - { - offset += ZipConstants.CryptoHeaderSize; - - InitializePassword(Password); - - byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; - var rng = RandomNumberGenerator.Create(); - rng.GetBytes(cryptBuffer); - - cryptBuffer[11] = (byte)(crcValue >> 24); - - EncryptBlock(cryptBuffer, 0, cryptBuffer.Length); - baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length); - } - - private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData) - { - // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored. - const int VENDOR_VERSION = 2; - // Vendor ID is the two ASCII characters "AE". - const int VENDOR_ID = 0x4541; //not 6965; - extraData.StartNewEntry(); - // Pack AES extra data field see http://www.winzip.com/aes_info.htm - //extraData.AddLeShort(7); // Data size (currently 7) - extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2 - extraData.AddLeShort(VENDOR_ID); // "AE" - extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256 - extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file - extraData.AddNewEntry(0x9901); - } - - // Replaces WriteEncryptionHeader for AES - // - private void WriteAESHeader(ZipEntry entry) - { - byte[] salt; - byte[] pwdVerifier; - InitializeAESPassword(entry, Password, out salt, out pwdVerifier); - // File format for AES: - // Size (bytes) Content - // ------------ ------- - // Variable Salt value - // 2 Password verification value - // Variable Encrypted file data - // 10 Authentication code - // - // Value in the "compressed size" fields of the local file header and the central directory entry - // is the total size of all the items listed above. In other words, it is the total size of the - // salt value, password verification value, encrypted data, and authentication code. - baseOutputStream_.Write(salt, 0, salt.Length); - baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length); - } - - /// - /// Writes the given buffer to the current entry. - /// - /// The buffer containing data to write. - /// The offset of the first byte to write. - /// The number of bytes to write. - /// Archive size is invalid - /// No entry is active. - public override void Write(byte[] buffer, int offset, int count) - { - if (curEntry == null) - { - throw new InvalidOperationException("No open entry."); - } - - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); - } - - if ((buffer.Length - offset) < count) - { - throw new ArgumentException("Invalid offset/count combination"); - } - - if (curEntry.AESKeySize == 0) - { - // Only update CRC if AES is not enabled - crc.Update(new ArraySegment(buffer, offset, count)); - } - - size += count; - - switch (curMethod) - { - case CompressionMethod.Deflated: - base.Write(buffer, offset, count); - break; - - case CompressionMethod.Stored: - if (Password != null) - { - CopyAndEncrypt(buffer, offset, count); - } - else - { - baseOutputStream_.Write(buffer, offset, count); - } - break; - } - } - - private void CopyAndEncrypt(byte[] buffer, int offset, int count) - { - const int CopyBufferSize = 4096; - byte[] localBuffer = new byte[CopyBufferSize]; - while (count > 0) - { - int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize; - - Array.Copy(buffer, offset, localBuffer, 0, bufferCount); - EncryptBlock(localBuffer, 0, bufferCount); - baseOutputStream_.Write(localBuffer, 0, bufferCount); - count -= bufferCount; - offset += bufferCount; - } - } - - /// - /// Finishes the stream. This will write the central directory at the - /// end of the zip file and flush the stream. - /// - /// - /// This is automatically called when the stream is closed. - /// - /// - /// An I/O error occurs. - /// - /// - /// Comment exceeds the maximum length
- /// Entry name exceeds the maximum length - ///
- public override void Finish() - { - if (entries == null) - { - return; - } - - if (curEntry != null) - { - CloseEntry(); - } - - long numEntries = entries.Count; - long sizeEntries = 0; - - foreach (ZipEntry entry in entries) - { - WriteLeInt(ZipConstants.CentralHeaderSignature); - WriteLeShort((entry.HostSystem << 8) | entry.VersionMadeBy); - WriteLeShort(entry.Version); - WriteLeShort(entry.Flags); - WriteLeShort((short)entry.CompressionMethodForHeader); - WriteLeInt((int)entry.DosTime); - WriteLeInt((int)entry.Crc); - - if (entry.IsZip64Forced() || - (entry.CompressedSize >= uint.MaxValue)) - { - WriteLeInt(-1); - } - else - { - WriteLeInt((int)entry.CompressedSize); - } - - if (entry.IsZip64Forced() || - (entry.Size >= uint.MaxValue)) - { - WriteLeInt(-1); - } - else - { - WriteLeInt((int)entry.Size); - } - - byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); - - if (name.Length > 0xffff) - { - throw new ZipException("Name too long."); - } - - var ed = new ZipExtraData(entry.ExtraData); - - if (entry.CentralHeaderRequiresZip64) - { - ed.StartNewEntry(); - if (entry.IsZip64Forced() || - (entry.Size >= 0xffffffff)) - { - ed.AddLeLong(entry.Size); - } - - if (entry.IsZip64Forced() || - (entry.CompressedSize >= 0xffffffff)) - { - ed.AddLeLong(entry.CompressedSize); - } - - if (entry.Offset >= 0xffffffff) - { - ed.AddLeLong(entry.Offset); - } - - ed.AddNewEntry(1); - } - else - { - ed.Delete(1); - } - - if (entry.AESKeySize > 0) - { - AddExtraDataAES(entry, ed); - } - byte[] extra = ed.GetEntryData(); - - byte[] entryComment = - (entry.Comment != null) ? - ZipStrings.ConvertToArray(entry.Flags, entry.Comment) : - Empty.Array(); - - if (entryComment.Length > 0xffff) - { - throw new ZipException("Comment too long."); - } - - WriteLeShort(name.Length); - WriteLeShort(extra.Length); - WriteLeShort(entryComment.Length); - WriteLeShort(0); // disk number - WriteLeShort(0); // internal file attributes - // external file attributes - - if (entry.ExternalFileAttributes != -1) - { - WriteLeInt(entry.ExternalFileAttributes); - } - else - { - if (entry.IsDirectory) - { // mark entry as directory (from nikolam.AT.perfectinfo.com) - WriteLeInt(16); - } - else - { - WriteLeInt(0); - } - } - - if (entry.Offset >= uint.MaxValue) - { - WriteLeInt(-1); - } - else - { - WriteLeInt((int)entry.Offset); - } - - if (name.Length > 0) - { - baseOutputStream_.Write(name, 0, name.Length); - } - - if (extra.Length > 0) - { - baseOutputStream_.Write(extra, 0, extra.Length); - } - - if (entryComment.Length > 0) - { - baseOutputStream_.Write(entryComment, 0, entryComment.Length); - } - - sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length; - } - - using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_)) - { - zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment); - } - - entries = null; - } - - /// - /// Flushes the stream by calling Flush on the deflater stream unless - /// the current compression method is . Then it flushes the underlying output stream. - /// - public override void Flush() - { - if(curMethod == CompressionMethod.Stored) - { - baseOutputStream_.Flush(); - } - else - { - base.Flush(); - } - } - - #region Instance Fields - - /// - /// The entries for the archive. - /// - private List entries = new List(); - - /// - /// Used to track the crc of data added to entries. - /// - private Crc32 crc = new Crc32(); - - /// - /// The current entry being added. - /// - private ZipEntry curEntry; - - private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION; - - private CompressionMethod curMethod = CompressionMethod.Deflated; - - /// - /// Used to track the size of data for an entry during writing. - /// - private long size; - - /// - /// Offset to be recorded for each entry in the central header. - /// - private long offset; - - /// - /// Comment for the entire archive recorded in central header. - /// - private byte[] zipComment = Empty.Array(); - - /// - /// Flag indicating that header patching is required for the current entry. - /// - private bool patchEntryHeader; - - /// - /// Position to patch crc - /// - private long crcPatchPos = -1; - - /// - /// Position to patch size. - /// - private long sizePatchPos = -1; - - // Default is dynamic which is not backwards compatible and can cause problems - // with XP's built in compression which cant read Zip64 archives. - // However it does avoid the situation were a large file is added and cannot be completed correctly. - // NOTE: Setting the size for entries before they are added is the best solution! - private UseZip64 useZip64_ = UseZip64.Dynamic; - - /// - /// The password to use when encrypting archive entries. - /// - private string password; - - #endregion Instance Fields - - #region Static Fields - - // Static to help ensure that multiple files within a zip will get different random salt - private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create(); - - #endregion Static Fields - } + #region Constructors + + /// + /// Creates a new Zip output stream, writing a zip archive. + /// + /// + /// The output stream to which the archive contents are written. + /// + public ZipOutputStream(Stream baseOutputStream) + : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)) + { + } + + /// + /// Creates a new Zip output stream, writing a zip archive. + /// + /// The output stream to which the archive contents are written. + /// Size of the buffer to use. + public ZipOutputStream(Stream baseOutputStream, int bufferSize) + : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize) + { + } + + #endregion Constructors + + /// + /// Gets a flag value of true if the central header has been added for this archive; false if it has not been added. + /// + /// No further entries can be added once this has been done. + public bool IsFinished + { + get + { + return entries == null; + } + } + + /// + /// Set the zip file comment. + /// + /// + /// The comment text for the entire archive. + /// + /// + /// The converted comment is longer than 0xffff bytes. + /// + public void SetComment(string comment) + { + // TODO: Its not yet clear how to handle unicode comments here. + var commentBytes = ZipStrings.ConvertToArray(comment); + if (commentBytes.Length > 0xffff) + { + throw new ArgumentOutOfRangeException(nameof(comment)); + } + zipComment = commentBytes; + } + + /// + /// Sets the compression level. The new level will be activated + /// immediately. + /// + /// The new compression level (1 to 9). + /// + /// Level specified is not supported. + /// + /// + public void SetLevel(int level) + { + deflater_.SetLevel(level); + defaultCompressionLevel = level; + } + + /// + /// Get the current deflater compression level + /// + /// The current compression level + public int GetLevel() + { + return deflater_.GetLevel(); + } + + /// + /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries. + /// + /// Older archivers may not understand Zip64 extensions. + /// If backwards compatability is an issue be careful when adding entries to an archive. + /// Setting this property to off is workable but less desirable as in those circumstances adding a file + /// larger then 4GB will fail. + public UseZip64 UseZip64 + { + get { return useZip64_; } + set { useZip64_ = value; } + } + + /// + /// Used for transforming the names of entries added by . + /// Defaults to , set to null to disable transforms and use names as supplied. + /// + public INameTransform NameTransform { get; set; } = new PathTransformer(); + + /// + /// Get/set the password used for encryption. + /// + /// When set to null or if the password is empty no encryption is performed + public string Password + { + get + { + return password; + } + set + { + password = (value != null) && (value.Length == 0) ? null : value; + } + } + + /// + /// Write an unsigned short in little endian byte order. + /// + private void WriteLeShort(int value) + { + unchecked + { + baseOutputStream_.WriteByte((byte)(value & 0xff)); + baseOutputStream_.WriteByte((byte)((value >> 8) & 0xff)); + } + } + + /// + /// Write an int in little endian byte order. + /// + private void WriteLeInt(int value) + { + unchecked + { + WriteLeShort(value); + WriteLeShort(value >> 16); + } + } + + /// + /// Write an int in little endian byte order. + /// + private void WriteLeLong(long value) + { + unchecked + { + WriteLeInt((int)value); + WriteLeInt((int)(value >> 32)); + } + } + + // Apply any configured transforms/cleaning to the name of the supplied entry. + private void TransformEntryName(ZipEntry entry) + { + if (this.NameTransform != null) + { + entry.Name = entry.IsDirectory ? this.NameTransform.TransformDirectory(entry.Name) : this.NameTransform.TransformFile(entry.Name); + } + } + + /// + /// Starts a new Zip entry. It automatically closes the previous + /// entry if present. + /// All entry elements bar name are optional, but must be correct if present. + /// If the compression method is stored and the output is not patchable + /// the compression for that entry is automatically changed to deflate level 0 + /// + /// + /// the entry. + /// + /// + /// if entry passed is null. + /// + /// + /// if an I/O error occured. + /// + /// + /// if stream was finished + /// + /// + /// Too many entries in the Zip file
+ /// Entry name is too long
+ /// Finish has already been called
+ ///
+ /// + /// The Compression method specified for the entry is unsupported. + /// + public void PutNextEntry(ZipEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException(nameof(entry)); + } + + if (entries == null) + { + throw new InvalidOperationException("ZipOutputStream was finished"); + } + + if (curEntry != null) + { + CloseEntry(); + } + + if (entries.Count == int.MaxValue) + { + throw new ZipException("Too many entries for Zip file"); + } + + var method = entry.CompressionMethod; + + // Check that the compression is one that we support + if (method is not CompressionMethod.Deflated and not CompressionMethod.Stored) + { + throw new NotImplementedException("Compression method not supported"); + } + + // A password must have been set in order to add AES encrypted entries + if (entry.AESKeySize > 0 && string.IsNullOrEmpty(this.Password)) + { + throw new InvalidOperationException("The Password property must be set before AES encrypted entries can be added"); + } + + var compressionLevel = defaultCompressionLevel; + + // Clear flags that the library manages internally + entry.Flags &= (int)GeneralBitFlags.UnicodeText; + patchEntryHeader = false; + + bool headerInfoAvailable; + + // No need to compress - definitely no data. + if (entry.Size == 0) + { + entry.CompressedSize = entry.Size; + entry.Crc = 0; + method = CompressionMethod.Stored; + headerInfoAvailable = true; + } + else + { + headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0; + + // Switch to deflation if storing isnt possible. + if (method == CompressionMethod.Stored) + { + if (!headerInfoAvailable) + { + if (!CanPatchEntries) + { + // Can't patch entries so storing is not possible. + method = CompressionMethod.Deflated; + compressionLevel = 0; + } + } + else // entry.size must be > 0 + { + entry.CompressedSize = entry.Size; + headerInfoAvailable = entry.HasCrc; + } + } + } + + if (headerInfoAvailable == false) + { + if (CanPatchEntries == false) + { + // Only way to record size and compressed size is to append a data descriptor + // after compressed data. + + // Stored entries of this form have already been converted to deflating. + entry.Flags |= 8; + } + else + { + patchEntryHeader = true; + } + } + + if (Password != null) + { + entry.IsCrypted = true; + if (entry.Crc < 0) + { + // Need to append a data descriptor as the crc isnt available for use + // with encryption, the date is used instead. Setting the flag + // indicates this to the decompressor. + entry.Flags |= 8; + } + } + + entry.Offset = offset; + entry.CompressionMethod = method; + + curMethod = method; + sizePatchPos = -1; + + if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic))) + { + entry.ForceZip64(); + } + + // Write the local file header + WriteLeInt(ZipConstants.LocalHeaderSignature); + + WriteLeShort(entry.Version); + WriteLeShort(entry.Flags); + WriteLeShort((byte)entry.CompressionMethodForHeader); + WriteLeInt((int)entry.DosTime); + + // TODO: Refactor header writing. Its done in several places. + if (headerInfoAvailable) + { + WriteLeInt((int)entry.Crc); + if (entry.LocalHeaderRequiresZip64) + { + WriteLeInt(-1); + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.CompressedSize + entry.EncryptionOverheadSize); + WriteLeInt((int)entry.Size); + } + } + else + { + if (patchEntryHeader) + { + crcPatchPos = baseOutputStream_.Position; + } + WriteLeInt(0); // Crc + + if (patchEntryHeader) + { + sizePatchPos = baseOutputStream_.Position; + } + + // For local header both sizes appear in Zip64 Extended Information + if (entry.LocalHeaderRequiresZip64 || patchEntryHeader) + { + WriteLeInt(-1); + WriteLeInt(-1); + } + else + { + WriteLeInt(0); // Compressed size + WriteLeInt(0); // Uncompressed size + } + } + + // Apply any required transforms to the entry name, and then convert to byte array format. + TransformEntryName(entry); + var name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xFFFF) + { + throw new ZipException("Entry name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.LocalHeaderRequiresZip64) + { + ed.StartNewEntry(); + if (headerInfoAvailable) + { + ed.AddLeLong(entry.Size); + ed.AddLeLong(entry.CompressedSize + entry.EncryptionOverheadSize); + } + else + { + ed.AddLeLong(-1); + ed.AddLeLong(-1); + } + ed.AddNewEntry(1); + + if (!ed.Find(1)) + { + throw new ZipException("Internal error cant find extra data"); + } + + if (patchEntryHeader) + { + sizePatchPos = ed.CurrentReadIndex; + } + } + else + { + ed.Delete(1); + } + + if (entry.AESKeySize > 0) + { + AddExtraDataAES(entry, ed); + } + var extra = ed.GetEntryData(); + + WriteLeShort(name.Length); + WriteLeShort(extra.Length); + + if (name.Length > 0) + { + baseOutputStream_.Write(name, 0, name.Length); + } + + if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) + { + sizePatchPos += baseOutputStream_.Position; + } + + if (extra.Length > 0) + { + baseOutputStream_.Write(extra, 0, extra.Length); + } + + offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length; + // Fix offsetOfCentraldir for AES + if (entry.AESKeySize > 0) + offset += entry.AESOverheadSize; + + // Activate the entry. + curEntry = entry; + crc.Reset(); + if (method == CompressionMethod.Deflated) + { + deflater_.Reset(); + deflater_.SetLevel(compressionLevel); + } + size = 0; + + if (entry.IsCrypted) + { + if (entry.AESKeySize > 0) + { + WriteAESHeader(entry); + } + else + { + if (entry.Crc < 0) + { // so testing Zip will says its ok + WriteEncryptionHeader(entry.DosTime << 16); + } + else + { + WriteEncryptionHeader(entry.Crc); + } + } + } + } + + /// + /// Closes the current entry, updating header and footer information as required + /// + /// + /// Invalid entry field values. + /// + /// + /// An I/O error occurs. + /// + /// + /// No entry is active. + /// + public void CloseEntry() + { + if (curEntry == null) + { + throw new InvalidOperationException("No open entry"); + } + + var csize = size; + + // First finish the deflater, if appropriate + if (curMethod == CompressionMethod.Deflated) + { + if (size >= 0) + { + base.Finish(); + csize = deflater_.TotalOut; + } + else + { + deflater_.Reset(); + } + } + else if (curMethod == CompressionMethod.Stored) + { + // This is done by Finish() for Deflated entries, but we need to do it + // ourselves for Stored ones + base.GetAuthCodeIfAES(); + } + + // Write the AES Authentication Code (a hash of the compressed and encrypted data) + if (curEntry.AESKeySize > 0) + { + baseOutputStream_.Write(AESAuthCode, 0, 10); + // Always use 0 as CRC for AE-2 format + curEntry.Crc = 0; + } + else + { + if (curEntry.Crc < 0) + { + curEntry.Crc = crc.Value; + } + else if (curEntry.Crc != crc.Value) + { + throw new ZipException($"crc was {crc.Value}, but {curEntry.Crc} was expected"); + } + } + + if (curEntry.Size < 0) + { + curEntry.Size = size; + } + else if (curEntry.Size != size) + { + throw new ZipException($"size was {size}, but {curEntry.Size} was expected"); + } + + if (curEntry.CompressedSize < 0) + { + curEntry.CompressedSize = csize; + } + else if (curEntry.CompressedSize != csize) + { + throw new ZipException($"compressed size was {csize}, but {curEntry.CompressedSize} expected"); + } + + offset += csize; + + if (curEntry.IsCrypted) + { + curEntry.CompressedSize += curEntry.EncryptionOverheadSize; + } + + // Patch the header if possible + if (patchEntryHeader) + { + patchEntryHeader = false; + + var curPos = baseOutputStream_.Position; + baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin); + WriteLeInt((int)curEntry.Crc); + + if (curEntry.LocalHeaderRequiresZip64) + { + if (sizePatchPos == -1) + { + throw new ZipException("Entry requires zip64 but this has been turned off"); + } + + baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin); + WriteLeLong(curEntry.Size); + WriteLeLong(curEntry.CompressedSize); + } + else + { + WriteLeInt((int)curEntry.CompressedSize); + WriteLeInt((int)curEntry.Size); + } + baseOutputStream_.Seek(curPos, SeekOrigin.Begin); + } + + // Add data descriptor if flagged as required + if ((curEntry.Flags & 8) != 0) + { + WriteLeInt(ZipConstants.DataDescriptorSignature); + WriteLeInt(unchecked((int)curEntry.Crc)); + + if (curEntry.LocalHeaderRequiresZip64) + { + WriteLeLong(curEntry.CompressedSize); + WriteLeLong(curEntry.Size); + offset += ZipConstants.Zip64DataDescriptorSize; + } + else + { + WriteLeInt((int)curEntry.CompressedSize); + WriteLeInt((int)curEntry.Size); + offset += ZipConstants.DataDescriptorSize; + } + } + + entries.Add(curEntry); + curEntry = null; + } + + /// + /// Initializes encryption keys based on given . + /// + /// The password. + private void InitializePassword(string password) + { + var pkManaged = new PkzipClassicManaged(); + var key = PkzipClassic.GenerateKeys(ZipStrings.ConvertToArray(password)); + cryptoTransform_ = pkManaged.CreateEncryptor(key, null); + } + + /// + /// Initializes encryption keys based on given password. + /// + private void InitializeAESPassword(ZipEntry entry, string rawPassword, + out byte[] salt, out byte[] pwdVerifier) + { + salt = new byte[entry.AESSaltLen]; + + // Salt needs to be cryptographically random, and unique per file + _aesRnd.GetBytes(salt); + + var blockSize = entry.AESKeySize / 8; // bits to bytes + + cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true); + pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier; + } + + private void WriteEncryptionHeader(long crcValue) + { + offset += ZipConstants.CryptoHeaderSize; + + InitializePassword(Password); + + var cryptBuffer = new byte[ZipConstants.CryptoHeaderSize]; + var rng = RandomNumberGenerator.Create(); + rng.GetBytes(cryptBuffer); + + cryptBuffer[11] = (byte)(crcValue >> 24); + + EncryptBlock(cryptBuffer, 0, cryptBuffer.Length); + baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length); + } + + private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData) + { + // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored. + const int VENDOR_VERSION = 2; + // Vendor ID is the two ASCII characters "AE". + const int VENDOR_ID = 0x4541; //not 6965; + extraData.StartNewEntry(); + // Pack AES extra data field see http://www.winzip.com/aes_info.htm + //extraData.AddLeShort(7); // Data size (currently 7) + extraData.AddLeShort(VENDOR_VERSION); // 2 = AE-2 + extraData.AddLeShort(VENDOR_ID); // "AE" + extraData.AddData(entry.AESEncryptionStrength); // 1 = 128, 2 = 192, 3 = 256 + extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file + extraData.AddNewEntry(0x9901); + } + + // Replaces WriteEncryptionHeader for AES + // + private void WriteAESHeader(ZipEntry entry) + { + byte[] salt; + byte[] pwdVerifier; + InitializeAESPassword(entry, Password, out salt, out pwdVerifier); + // File format for AES: + // Size (bytes) Content + // ------------ ------- + // Variable Salt value + // 2 Password verification value + // Variable Encrypted file data + // 10 Authentication code + // + // Value in the "compressed size" fields of the local file header and the central directory entry + // is the total size of all the items listed above. In other words, it is the total size of the + // salt value, password verification value, encrypted data, and authentication code. + baseOutputStream_.Write(salt, 0, salt.Length); + baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length); + } + + /// + /// Writes the given buffer to the current entry. + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// Archive size is invalid + /// No entry is active. + public override void Write(byte[] buffer, int offset, int count) + { + if (curEntry == null) + { + throw new InvalidOperationException("No open entry."); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative"); + } + + if ((buffer.Length - offset) < count) + { + throw new ArgumentException("Invalid offset/count combination"); + } + + if (curEntry.AESKeySize == 0) + { + // Only update CRC if AES is not enabled + crc.Update(new ArraySegment(buffer, offset, count)); + } + + size += count; + + switch (curMethod) + { + case CompressionMethod.Deflated: + base.Write(buffer, offset, count); + break; + + case CompressionMethod.Stored: + if (Password != null) + { + CopyAndEncrypt(buffer, offset, count); + } + else + { + baseOutputStream_.Write(buffer, offset, count); + } + break; + } + } + + private void CopyAndEncrypt(byte[] buffer, int offset, int count) + { + const int CopyBufferSize = 4096; + var localBuffer = new byte[CopyBufferSize]; + while (count > 0) + { + var bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize; + + Array.Copy(buffer, offset, localBuffer, 0, bufferCount); + EncryptBlock(localBuffer, 0, bufferCount); + baseOutputStream_.Write(localBuffer, 0, bufferCount); + count -= bufferCount; + offset += bufferCount; + } + } + + /// + /// Finishes the stream. This will write the central directory at the + /// end of the zip file and flush the stream. + /// + /// + /// This is automatically called when the stream is closed. + /// + /// + /// An I/O error occurs. + /// + /// + /// Comment exceeds the maximum length
+ /// Entry name exceeds the maximum length + ///
+ public override void Finish() + { + if (entries == null) + { + return; + } + + if (curEntry != null) + { + CloseEntry(); + } + + long numEntries = entries.Count; + long sizeEntries = 0; + + foreach (var entry in entries) + { + WriteLeInt(ZipConstants.CentralHeaderSignature); + WriteLeShort((entry.HostSystem << 8) | entry.VersionMadeBy); + WriteLeShort(entry.Version); + WriteLeShort(entry.Flags); + WriteLeShort((short)entry.CompressionMethodForHeader); + WriteLeInt((int)entry.DosTime); + WriteLeInt((int)entry.Crc); + + if (entry.IsZip64Forced() || + (entry.CompressedSize >= uint.MaxValue)) + { + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.CompressedSize); + } + + if (entry.IsZip64Forced() || + (entry.Size >= uint.MaxValue)) + { + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.Size); + } + + var name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + + if (name.Length > 0xffff) + { + throw new ZipException("Name too long."); + } + + var ed = new ZipExtraData(entry.ExtraData); + + if (entry.CentralHeaderRequiresZip64) + { + ed.StartNewEntry(); + if (entry.IsZip64Forced() || + (entry.Size >= 0xffffffff)) + { + ed.AddLeLong(entry.Size); + } + + if (entry.IsZip64Forced() || + (entry.CompressedSize >= 0xffffffff)) + { + ed.AddLeLong(entry.CompressedSize); + } + + if (entry.Offset >= 0xffffffff) + { + ed.AddLeLong(entry.Offset); + } + + ed.AddNewEntry(1); + } + else + { + ed.Delete(1); + } + + if (entry.AESKeySize > 0) + { + AddExtraDataAES(entry, ed); + } + var extra = ed.GetEntryData(); + + var entryComment = + (entry.Comment != null) ? + ZipStrings.ConvertToArray(entry.Flags, entry.Comment) : + Empty.Array(); + + if (entryComment.Length > 0xffff) + { + throw new ZipException("Comment too long."); + } + + WriteLeShort(name.Length); + WriteLeShort(extra.Length); + WriteLeShort(entryComment.Length); + WriteLeShort(0); // disk number + WriteLeShort(0); // internal file attributes + // external file attributes + + if (entry.ExternalFileAttributes != -1) + { + WriteLeInt(entry.ExternalFileAttributes); + } + else + { + if (entry.IsDirectory) + { // mark entry as directory (from nikolam.AT.perfectinfo.com) + WriteLeInt(16); + } + else + { + WriteLeInt(0); + } + } + + if (entry.Offset >= uint.MaxValue) + { + WriteLeInt(-1); + } + else + { + WriteLeInt((int)entry.Offset); + } + + if (name.Length > 0) + { + baseOutputStream_.Write(name, 0, name.Length); + } + + if (extra.Length > 0) + { + baseOutputStream_.Write(extra, 0, extra.Length); + } + + if (entryComment.Length > 0) + { + baseOutputStream_.Write(entryComment, 0, entryComment.Length); + } + + sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length; + } + + using (var zhs = new ZipHelperStream(baseOutputStream_)) + { + zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment); + } + + entries = null; + } + + /// + /// Flushes the stream by calling Flush on the deflater stream unless + /// the current compression method is . Then it flushes the underlying output stream. + /// + public override void Flush() + { + if (curMethod == CompressionMethod.Stored) + { + baseOutputStream_.Flush(); + } + else + { + base.Flush(); + } + } + + #region Instance Fields + + /// + /// The entries for the archive. + /// + private List entries = []; + + /// + /// Used to track the crc of data added to entries. + /// + private readonly Crc32 crc = new(); + + /// + /// The current entry being added. + /// + private ZipEntry curEntry; + + private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION; + + private CompressionMethod curMethod = CompressionMethod.Deflated; + + /// + /// Used to track the size of data for an entry during writing. + /// + private long size; + + /// + /// Offset to be recorded for each entry in the central header. + /// + private long offset; + + /// + /// Comment for the entire archive recorded in central header. + /// + private byte[] zipComment = Empty.Array(); + + /// + /// Flag indicating that header patching is required for the current entry. + /// + private bool patchEntryHeader; + + /// + /// Position to patch crc + /// + private long crcPatchPos = -1; + + /// + /// Position to patch size. + /// + private long sizePatchPos = -1; + + // Default is dynamic which is not backwards compatible and can cause problems + // with XP's built in compression which cant read Zip64 archives. + // However it does avoid the situation were a large file is added and cannot be completed correctly. + // NOTE: Setting the size for entries before they are added is the best solution! + private UseZip64 useZip64_ = UseZip64.Dynamic; + + /// + /// The password to use when encrypting archive entries. + /// + private string password; + + #endregion Instance Fields + + #region Static Fields + + // Static to help ensure that multiple files within a zip will get different random salt + private static readonly RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create(); + + #endregion Static Fields } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs index 9cf4cc791..284f2ffae 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs +++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs @@ -1,194 +1,186 @@ -using System; +using MelonLoader.ICSharpCode.SharpZipLib.Core; +using System; using System.Text; -using MelonLoader.ICSharpCode.SharpZipLib.Core; -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; + +/// +/// This static class contains functions for encoding and decoding zip file strings +/// +public static class ZipStrings { - /// - /// This static class contains functions for encoding and decoding zip file strings - /// - public static class ZipStrings - { - static ZipStrings() - { - try - { - var platformCodepage = Encoding.GetEncoding(0).CodePage; - SystemDefaultCodePage = (platformCodepage == 1 || platformCodepage == 2 || platformCodepage == 3 || platformCodepage == 42) ? FallbackCodePage : platformCodepage; - } - catch - { - SystemDefaultCodePage = FallbackCodePage; - } - } - - /// Code page backing field - /// - /// The original Zip specification (https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) states - /// that file names should only be encoded with IBM Code Page 437 or UTF-8. - /// In practice, most zip apps use OEM or system encoding (typically cp437 on Windows). - /// Let's be good citizens and default to UTF-8 http://utf8everywhere.org/ - /// - private static int codePage = AutomaticCodePage; - - /// Automatically select codepage while opening archive - /// see https://github.com/icsharpcode/SharpZipLib/pull/280#issuecomment-433608324 - /// - private const int AutomaticCodePage = -1; - - /// - /// Encoding used for string conversion. Setting this to 65001 (UTF-8) will - /// also set the Language encoding flag to indicate UTF-8 encoded file names. - /// - public static int CodePage - { - get - { - return codePage == AutomaticCodePage? Encoding.UTF8.CodePage:codePage; - } - set - { - if ((value < 0) || (value > 65535) || - (value == 1) || (value == 2) || (value == 3) || (value == 42)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - codePage = value; - } - } - - private const int FallbackCodePage = 437; - - /// - /// Attempt to get the operating system default codepage, or failing that, to - /// the fallback code page IBM 437. - /// - public static int SystemDefaultCodePage { get; } - - /// - /// Get whether the default codepage is set to UTF-8. Setting this property to false will - /// set the to - /// - /// - /// Get OEM codepage from NetFX, which parses the NLP file with culture info table etc etc. - /// But sometimes it yields the special value of 1 which is nicknamed CodePageNoOEM in sources (might also mean CP_OEMCP, but Encoding puts it so). - /// This was observed on Ukranian and Hindu systems. - /// Given this value, throws an . - /// So replace it with , (IBM 437 which is the default code page in a default Windows installation console. - /// - public static bool UseUnicode - { - get - { - return codePage == Encoding.UTF8.CodePage; - } - set - { - if (value) - { - codePage = Encoding.UTF8.CodePage; - } - else - { - codePage = SystemDefaultCodePage; - } - } - } - - /// - /// Convert a portion of a byte array to a string using - /// - /// - /// Data to convert to string - /// - /// - /// Number of bytes to convert starting from index 0 - /// - /// - /// data[0]..data[count - 1] converted to a string - /// - public static string ConvertToString(byte[] data, int count) - => data == null - ? string.Empty - : Encoding.GetEncoding(CodePage).GetString(data, 0, count); - - /// - /// Convert a byte array to a string using - /// - /// - /// Byte array to convert - /// - /// - /// dataconverted to a string - /// - public static string ConvertToString(byte[] data) - => ConvertToString(data, data.Length); - - private static Encoding EncodingFromFlag(int flags) - => ((flags & (int)GeneralBitFlags.UnicodeText) != 0) - ? Encoding.UTF8 - : Encoding.GetEncoding( - // if CodePage wasn't set manually and no utf flag present - // then we must use SystemDefault (old behavior) - // otherwise, CodePage should be preferred over SystemDefault - // see https://github.com/icsharpcode/SharpZipLib/issues/274 - codePage == AutomaticCodePage? - SystemDefaultCodePage: - codePage); - - /// - /// Convert a byte array to a string using - /// - /// The applicable general purpose bits flags - /// - /// Byte array to convert - /// - /// The number of bytes to convert. - /// - /// dataconverted to a string - /// - public static string ConvertToStringExt(int flags, byte[] data, int count) - => (data == null) - ? string.Empty - : EncodingFromFlag(flags).GetString(data, 0, count); - - /// - /// Convert a byte array to a string using - /// - /// - /// Byte array to convert - /// - /// The applicable general purpose bits flags - /// - /// dataconverted to a string - /// - public static string ConvertToStringExt(int flags, byte[] data) - => ConvertToStringExt(flags, data, data.Length); - - /// - /// Convert a string to a byte array using - /// - /// - /// String to convert to an array - /// - /// Converted array - public static byte[] ConvertToArray(string str) - => str == null - ? Empty.Array() - : Encoding.GetEncoding(CodePage).GetBytes(str); - - /// - /// Convert a string to a byte array using - /// - /// The applicable general purpose bits flags - /// - /// String to convert to an array - /// - /// Converted array - public static byte[] ConvertToArray(int flags, string str) - => (string.IsNullOrEmpty(str)) - ? Empty.Array() - : EncodingFromFlag(flags).GetBytes(str); - } + static ZipStrings() + { + try + { + var platformCodepage = Encoding.GetEncoding(0).CodePage; + SystemDefaultCodePage = (platformCodepage is 1 or 2 or 3 or 42) ? FallbackCodePage : platformCodepage; + } + catch + { + SystemDefaultCodePage = FallbackCodePage; + } + } + + /// Code page backing field + /// + /// The original Zip specification (https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) states + /// that file names should only be encoded with IBM Code Page 437 or UTF-8. + /// In practice, most zip apps use OEM or system encoding (typically cp437 on Windows). + /// Let's be good citizens and default to UTF-8 http://utf8everywhere.org/ + /// + private static int codePage = AutomaticCodePage; + + /// Automatically select codepage while opening archive + /// see https://github.com/icsharpcode/SharpZipLib/pull/280#issuecomment-433608324 + /// + private const int AutomaticCodePage = -1; + + /// + /// Encoding used for string conversion. Setting this to 65001 (UTF-8) will + /// also set the Language encoding flag to indicate UTF-8 encoded file names. + /// + public static int CodePage + { + get + { + return codePage == AutomaticCodePage ? Encoding.UTF8.CodePage : codePage; + } + set + { + if (value is < 0 or > 65535 or + 1 or 2 or 3 or 42) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + codePage = value; + } + } + + private const int FallbackCodePage = 437; + + /// + /// Attempt to get the operating system default codepage, or failing that, to + /// the fallback code page IBM 437. + /// + public static int SystemDefaultCodePage { get; } + + /// + /// Get whether the default codepage is set to UTF-8. Setting this property to false will + /// set the to + /// + /// + /// Get OEM codepage from NetFX, which parses the NLP file with culture info table etc etc. + /// But sometimes it yields the special value of 1 which is nicknamed CodePageNoOEM in sources (might also mean CP_OEMCP, but Encoding puts it so). + /// This was observed on Ukranian and Hindu systems. + /// Given this value, throws an . + /// So replace it with , (IBM 437 which is the default code page in a default Windows installation console. + /// + public static bool UseUnicode + { + get + { + return codePage == Encoding.UTF8.CodePage; + } + set + { + codePage = value ? Encoding.UTF8.CodePage : SystemDefaultCodePage; + } + } + + /// + /// Convert a portion of a byte array to a string using + /// + /// + /// Data to convert to string + /// + /// + /// Number of bytes to convert starting from index 0 + /// + /// + /// data[0]..data[count - 1] converted to a string + /// + public static string ConvertToString(byte[] data, int count) + => data == null + ? string.Empty + : Encoding.GetEncoding(CodePage).GetString(data, 0, count); + + /// + /// Convert a byte array to a string using + /// + /// + /// Byte array to convert + /// + /// + /// dataconverted to a string + /// + public static string ConvertToString(byte[] data) + => ConvertToString(data, data.Length); + + private static Encoding EncodingFromFlag(int flags) + => ((flags & (int)GeneralBitFlags.UnicodeText) != 0) + ? Encoding.UTF8 + : Encoding.GetEncoding( + // if CodePage wasn't set manually and no utf flag present + // then we must use SystemDefault (old behavior) + // otherwise, CodePage should be preferred over SystemDefault + // see https://github.com/icsharpcode/SharpZipLib/issues/274 + codePage == AutomaticCodePage ? + SystemDefaultCodePage : + codePage); + + /// + /// Convert a byte array to a string using + /// + /// The applicable general purpose bits flags + /// + /// Byte array to convert + /// + /// The number of bytes to convert. + /// + /// dataconverted to a string + /// + public static string ConvertToStringExt(int flags, byte[] data, int count) + => (data == null) + ? string.Empty + : EncodingFromFlag(flags).GetString(data, 0, count); + + /// + /// Convert a byte array to a string using + /// + /// + /// Byte array to convert + /// + /// The applicable general purpose bits flags + /// + /// dataconverted to a string + /// + public static string ConvertToStringExt(int flags, byte[] data) + => ConvertToStringExt(flags, data, data.Length); + + /// + /// Convert a string to a byte array using + /// + /// + /// String to convert to an array + /// + /// Converted array + public static byte[] ConvertToArray(string str) + => str == null + ? Empty.Array() + : Encoding.GetEncoding(CodePage).GetBytes(str); + + /// + /// Convert a string to a byte array using + /// + /// The applicable general purpose bits flags + /// + /// String to convert to an array + /// + /// Converted array + public static byte[] ConvertToArray(int flags, string str) + => string.IsNullOrEmpty(str) + ? Empty.Array() + : EncodingFromFlag(flags).GetBytes(str); } diff --git a/MelonLoader/ISupportModule_From.cs b/MelonLoader/ISupportModule_From.cs index 6b0a6054c..b6987f6d2 100644 --- a/MelonLoader/ISupportModule_From.cs +++ b/MelonLoader/ISupportModule_From.cs @@ -1,17 +1,16 @@ -namespace MelonLoader +namespace MelonLoader; + +public interface ISupportModule_From { - public interface ISupportModule_From - { - void OnApplicationLateStart(); - void OnSceneWasLoaded(int buildIndex, string sceneName); - void OnSceneWasInitialized(int buildIndex, string sceneName); - void OnSceneWasUnloaded(int buildIndex, string sceneName); - void Update(); - void FixedUpdate(); - void LateUpdate(); - void OnGUI(); - void Quit(); - void DefiniteQuit(); - void SetInteropSupportInterface(InteropSupport.Interface interop); - } + void OnApplicationLateStart(); + void OnSceneWasLoaded(int buildIndex, string sceneName); + void OnSceneWasInitialized(int buildIndex, string sceneName); + void OnSceneWasUnloaded(int buildIndex, string sceneName); + void Update(); + void FixedUpdate(); + void LateUpdate(); + void OnGUI(); + void Quit(); + void DefiniteQuit(); + void SetInteropSupportInterface(InteropSupport.Interface interop); } \ No newline at end of file diff --git a/MelonLoader/ISupportModule_To.cs b/MelonLoader/ISupportModule_To.cs index 1095eb523..9dcc1f262 100644 --- a/MelonLoader/ISupportModule_To.cs +++ b/MelonLoader/ISupportModule_To.cs @@ -1,11 +1,10 @@ using System.Collections; -namespace MelonLoader +namespace MelonLoader; + +public interface ISupportModule_To { - public interface ISupportModule_To - { - object StartCoroutine(IEnumerator coroutine); - void StopCoroutine(object coroutineToken); - void UnityDebugLog(string msg); - } + object StartCoroutine(IEnumerator coroutine); + void StopCoroutine(object coroutineToken); + void UnityDebugLog(string msg); } \ No newline at end of file diff --git a/MelonLoader/IniFile.cs b/MelonLoader/IniFile.cs index e5c996b90..6f006ae6c 100644 --- a/MelonLoader/IniFile.cs +++ b/MelonLoader/IniFile.cs @@ -2,69 +2,76 @@ using System.Runtime.InteropServices; using System.Text; -namespace MelonLoader +namespace MelonLoader; + +public class IniFile { - public class IniFile + [DllImport("KERNEL32.DLL", EntryPoint = "GetPrivateProfileStringW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] + private static extern int GetPrivateProfileString(string lpSection, string lpKey, string lpDefault, StringBuilder lpReturnString, int nSize, string lpFileName); + [DllImport("KERNEL32.DLL", EntryPoint = "WritePrivateProfileStringW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] + private static extern int WritePrivateProfileString(string lpSection, string lpKey, string lpValue, string lpFileName); + private string _path = ""; + public string Path { - [DllImport("KERNEL32.DLL", EntryPoint = "GetPrivateProfileStringW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] - private static extern int GetPrivateProfileString(string lpSection, string lpKey, string lpDefault, StringBuilder lpReturnString, int nSize, string lpFileName); - [DllImport("KERNEL32.DLL", EntryPoint = "WritePrivateProfileStringW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] - private static extern int WritePrivateProfileString(string lpSection, string lpKey, string lpValue, string lpFileName); - private string _path = ""; - public string Path { get { return _path; } internal set { if (!File.Exists(value)) File.WriteAllText(value, "", Encoding.Unicode); _path = value; } } - public IniFile(string INIPath) { Path = INIPath; } - private void WriteValue(string Section, string Key, string Value) { WritePrivateProfileString(Section, Key, Value, Path); } - private string ReadValue(string Section, string Key) + get { return _path; } + internal set { - const int MAX_CHARS = 1023; - StringBuilder result = new StringBuilder(MAX_CHARS); - GetPrivateProfileString(Section, Key, " _", result, MAX_CHARS, Path); - if (result.ToString().Equals(" _")) return null; - return result.ToString(); + if (!File.Exists(value)) + File.WriteAllText(value, "", Encoding.Unicode); + _path = value; } + } + public IniFile(string INIPath) { Path = INIPath; } + private void WriteValue(string Section, string Key, string Value) { WritePrivateProfileString(Section, Key, Value, Path); } + private string ReadValue(string Section, string Key) + { + const int MAX_CHARS = 1023; + var result = new StringBuilder(MAX_CHARS); + GetPrivateProfileString(Section, Key, " _", result, MAX_CHARS, Path); + return result.ToString().Equals(" _") ? null : result.ToString(); + } - public bool HasKey(string section, string name) { return ReadValue(section, name) != null; } + public bool HasKey(string section, string name) { return ReadValue(section, name) != null; } - public string GetString(string section, string name, string defaultValue = "", bool autoSave = false) - { - string value = ReadValue(section, name); - if (!string.IsNullOrEmpty(value)) - return value; - else if (autoSave) - SetString(section, name, defaultValue); - return defaultValue; - } - public void SetString(string section, string name, string value) { WriteValue(section, name, value.Trim()); } + public string GetString(string section, string name, string defaultValue = "", bool autoSave = false) + { + var value = ReadValue(section, name); + if (!string.IsNullOrEmpty(value)) + return value; + else if (autoSave) + SetString(section, name, defaultValue); + return defaultValue; + } + public void SetString(string section, string name, string value) { WriteValue(section, name, value.Trim()); } - public int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false) - { - if (int.TryParse(ReadValue(section, name), out int value)) - return value; - else if (autoSave) - SetInt(section, name, defaultValue); - return defaultValue; - } - public void SetInt(string section, string name, int value) { WriteValue(section, name, value.ToString()); } + public int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false) + { + if (int.TryParse(ReadValue(section, name), out var value)) + return value; + else if (autoSave) + SetInt(section, name, defaultValue); + return defaultValue; + } + public void SetInt(string section, string name, int value) { WriteValue(section, name, value.ToString()); } - public float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false) - { - if (float.TryParse(ReadValue(section, name), out float value)) - return value; - else if (autoSave) - SetFloat(section, name, defaultValue); - return defaultValue; - } - public void SetFloat(string section, string name, float value) { WriteValue(section, name, value.ToString()); } + public float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false) + { + if (float.TryParse(ReadValue(section, name), out var value)) + return value; + else if (autoSave) + SetFloat(section, name, defaultValue); + return defaultValue; + } + public void SetFloat(string section, string name, float value) { WriteValue(section, name, value.ToString()); } - public bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false) - { - string sVal = GetString(section, name, null); - if ("true".Equals(sVal) || "1".Equals(sVal) || "0".Equals(sVal) || "false".Equals(sVal)) - return ("true".Equals(sVal) || "1".Equals(sVal)); - else if (autoSave) - SetBool(section, name, defaultValue); - return defaultValue; - } - public void SetBool(string section, string name, bool value) { WriteValue(section, name, value ? "true" : "false"); } + public bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false) + { + var sVal = GetString(section, name, null); + if ("true".Equals(sVal) || "1".Equals(sVal) || "0".Equals(sVal) || "false".Equals(sVal)) + return "true".Equals(sVal) || "1".Equals(sVal); + else if (autoSave) + SetBool(section, name, defaultValue); + return defaultValue; } + public void SetBool(string section, string name, bool value) { WriteValue(section, name, value ? "true" : "false"); } } \ No newline at end of file diff --git a/MelonLoader/InternalUtils/BootstrapInterop.cs b/MelonLoader/InternalUtils/BootstrapInterop.cs index de7f0deb3..6e67079aa 100644 --- a/MelonLoader/InternalUtils/BootstrapInterop.cs +++ b/MelonLoader/InternalUtils/BootstrapInterop.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.InteropServices; - #if NET6_0_OR_GREATER using MelonLoader.CoreClrUtils; using MelonLoader.InternalUtils; diff --git a/MelonLoader/InternalUtils/DependencyGraph.cs b/MelonLoader/InternalUtils/DependencyGraph.cs index 8c232ef92..eadaea099 100644 --- a/MelonLoader/InternalUtils/DependencyGraph.cs +++ b/MelonLoader/InternalUtils/DependencyGraph.cs @@ -1,284 +1,285 @@ -using System; +using MelonLoader.Resolver; +using System; using System.Collections.Generic; -using System.Text; -using System.Reflection; using System.IO; -using MelonLoader.Resolver; +using System.Reflection; +using System.Text; #if NET6_0_OR_GREATER using System.Runtime.Loader; #endif -namespace MelonLoader.InternalUtils +namespace MelonLoader.InternalUtils; + +internal class DependencyGraph where T : MelonBase { - internal class DependencyGraph where T : MelonBase + public static void TopologicalSort(IList melons) { - public static void TopologicalSort(IList melons) - { - if (melons.Count <= 0) - return; - DependencyGraph dependencyGraph = new DependencyGraph(melons); - melons.Clear(); - dependencyGraph.TopologicalSortInto(melons); - } + if (melons.Count <= 0) + return; + var dependencyGraph = new DependencyGraph(melons); + melons.Clear(); + dependencyGraph.TopologicalSortInto(melons); + } - private readonly Vertex[] vertices; + private readonly Vertex[] vertices; - private DependencyGraph(IList melons) - { - int size = melons.Count; - vertices = new Vertex[size]; - Dictionary nameLookup = new Dictionary(size); + private DependencyGraph(IList melons) + { + var size = melons.Count; + vertices = new Vertex[size]; + var nameLookup = new Dictionary(size); - // Create a vertex in the dependency graph for each Melon to load - for (int i = 0; i < size; ++i) - { - Assembly melonAssembly = melons[i].MelonAssembly.Assembly; - string melonName = melons[i].Info.Name; + // Create a vertex in the dependency graph for each Melon to load + for (var i = 0; i < size; ++i) + { + var melonAssembly = melons[i].MelonAssembly.Assembly; + var melonName = melons[i].Info.Name; - Vertex melonVertex = new Vertex(i, melons[i], melonName); - vertices[i] = melonVertex; - nameLookup[melonAssembly.GetName().Name] = melonVertex; - } + var melonVertex = new Vertex(i, melons[i], melonName); + vertices[i] = melonVertex; + nameLookup[melonAssembly.GetName().Name] = melonVertex; + } - // Add an edge for each dependency between Melons - SortedDictionary> melonsWithMissingDeps = new SortedDictionary>(); - SortedDictionary> melonsWithIncompatibilities = new SortedDictionary>(); - List missingDependencies = new List(); - List incompatibilities = new List(); - HashSet optionalDependencies = new HashSet(); - HashSet additionalDependencies = new HashSet(); + // Add an edge for each dependency between Melons + SortedDictionary> melonsWithMissingDeps = []; + SortedDictionary> melonsWithIncompatibilities = []; + List missingDependencies = []; + List incompatibilities = []; + HashSet optionalDependencies = []; + HashSet additionalDependencies = []; - foreach (Vertex melonVertex in vertices) + foreach (var melonVertex in vertices) + { + var melonAssembly = melonVertex.melon.MelonAssembly.Assembly; + missingDependencies.Clear(); + optionalDependencies.Clear(); + incompatibilities.Clear(); + additionalDependencies.Clear(); + + var optionals = (MelonOptionalDependenciesAttribute)Attribute.GetCustomAttribute(melonAssembly, typeof(MelonOptionalDependenciesAttribute)); + if (optionals != null + && optionals.AssemblyNames != null) + optionalDependencies.UnionWith(optionals.AssemblyNames); + + var additionals = (MelonAdditionalDependenciesAttribute)Attribute.GetCustomAttribute(melonAssembly, typeof(MelonAdditionalDependenciesAttribute)); + if (additionals != null + && additionals.AssemblyNames != null) + additionalDependencies.UnionWith(additionals.AssemblyNames); + + var incompatibleAssemblies = (MelonIncompatibleAssembliesAttribute)Attribute.GetCustomAttribute(melonAssembly, typeof(MelonIncompatibleAssembliesAttribute)); + if (incompatibleAssemblies != null + && incompatibleAssemblies.AssemblyNames != null) { - Assembly melonAssembly = melonVertex.melon.MelonAssembly.Assembly; - missingDependencies.Clear(); - optionalDependencies.Clear(); - incompatibilities.Clear(); - additionalDependencies.Clear(); - - MelonOptionalDependenciesAttribute optionals = (MelonOptionalDependenciesAttribute)Attribute.GetCustomAttribute(melonAssembly, typeof(MelonOptionalDependenciesAttribute)); - if (optionals != null - && optionals.AssemblyNames != null) - optionalDependencies.UnionWith(optionals.AssemblyNames); - - MelonAdditionalDependenciesAttribute additionals = (MelonAdditionalDependenciesAttribute)Attribute.GetCustomAttribute(melonAssembly, typeof(MelonAdditionalDependenciesAttribute)); - if (additionals != null - && additionals.AssemblyNames != null) - additionalDependencies.UnionWith(additionals.AssemblyNames); - - MelonIncompatibleAssembliesAttribute incompatibleAssemblies = (MelonIncompatibleAssembliesAttribute)Attribute.GetCustomAttribute(melonAssembly, typeof(MelonIncompatibleAssembliesAttribute)); - if (incompatibleAssemblies != null - && incompatibleAssemblies.AssemblyNames != null) - { - foreach (string name in incompatibleAssemblies.AssemblyNames) - foreach (Vertex v in vertices) + foreach (var name in incompatibleAssemblies.AssemblyNames) + foreach (var v in vertices) + { + var assemblyName = v.melon.MelonAssembly.Assembly.GetName(); + if (v != melonVertex + && assemblyName.Name == name) { - AssemblyName assemblyName = v.melon.MelonAssembly.Assembly.GetName(); - if (v != melonVertex - && assemblyName.Name == name) - { - incompatibilities.Add(assemblyName); - v.skipLoading = true; - } + incompatibilities.Add(assemblyName); + v.skipLoading = true; } - } + } + } - foreach (AssemblyName dependency in melonAssembly.GetReferencedAssemblies()) + foreach (var dependency in melonAssembly.GetReferencedAssemblies()) + { + if (nameLookup.TryGetValue(dependency.Name, out var dependencyVertex)) { - if (nameLookup.TryGetValue(dependency.Name, out Vertex dependencyVertex)) - { - if (!melonVertex.dependencies.Contains(dependencyVertex)) - melonVertex.dependencies.Add(dependencyVertex); - if (!dependencyVertex.dependents.Contains(melonVertex)) - dependencyVertex.dependents.Add(melonVertex); - } - else if (!TryLoad(dependency) - && !TryResolve(dependency) - && !optionalDependencies.Contains(dependency.Name) - && !missingDependencies.Contains(dependency)) - missingDependencies.Add(dependency); + if (!melonVertex.dependencies.Contains(dependencyVertex)) + melonVertex.dependencies.Add(dependencyVertex); + if (!dependencyVertex.dependents.Contains(melonVertex)) + dependencyVertex.dependents.Add(melonVertex); } + else if (!TryLoad(dependency) + && !TryResolve(dependency) + && !optionalDependencies.Contains(dependency.Name) + && !missingDependencies.Contains(dependency)) + missingDependencies.Add(dependency); + } - foreach (string dependencyName in additionalDependencies) + foreach (var dependencyName in additionalDependencies) + { + var dependency = new AssemblyName(dependencyName); + if (nameLookup.TryGetValue(dependencyName, out var dependencyVertex)) { - AssemblyName dependency = new AssemblyName(dependencyName); - if (nameLookup.TryGetValue(dependencyName, out Vertex dependencyVertex)) - { - if (!melonVertex.dependencies.Contains(dependencyVertex)) - melonVertex.dependencies.Add(dependencyVertex); - if (!dependencyVertex.dependents.Contains(melonVertex)) - dependencyVertex.dependents.Add(melonVertex); - } - else if (!TryLoad(dependency) - && !TryResolve(dependency) - && !missingDependencies.Contains(dependency)) - missingDependencies.Add(dependency); + if (!melonVertex.dependencies.Contains(dependencyVertex)) + melonVertex.dependencies.Add(dependencyVertex); + if (!dependencyVertex.dependents.Contains(melonVertex)) + dependencyVertex.dependents.Add(melonVertex); } - - if ((missingDependencies.Count > 0) - && !melonsWithMissingDeps.ContainsKey(melonVertex.melon.Info.Name)) - // melonVertex.skipLoading = true; - melonsWithMissingDeps.Add(melonVertex.melon.Info.Name, missingDependencies.ToArray()); - - if ((incompatibilities.Count > 0) - && !melonsWithIncompatibilities.ContainsKey(melonVertex.melon.Info.Name)) - melonsWithIncompatibilities.Add(melonVertex.melon.Info.Name, incompatibilities.ToArray()); + else if (!TryLoad(dependency) + && !TryResolve(dependency) + && !missingDependencies.Contains(dependency)) + missingDependencies.Add(dependency); } - // Some Melons are missing dependencies. Don't load these Melons and show an error message - if (melonsWithMissingDeps.Count > 0) - MelonLogger.Warning(BuildMissingDependencyMessage(melonsWithMissingDeps)); + if ((missingDependencies.Count > 0) + && !melonsWithMissingDeps.ContainsKey(melonVertex.melon.Info.Name)) + // melonVertex.skipLoading = true; + melonsWithMissingDeps.Add(melonVertex.melon.Info.Name, missingDependencies.ToArray()); - if (melonsWithIncompatibilities.Count > 0) - MelonLogger.Warning(BuildIncompatibleAssembliesMessage(melonsWithIncompatibilities)); + if ((incompatibilities.Count > 0) + && !melonsWithIncompatibilities.ContainsKey(melonVertex.melon.Info.Name)) + melonsWithIncompatibilities.Add(melonVertex.melon.Info.Name, incompatibilities.ToArray()); } - // Returns true if 'assembly' was already loaded or could be loaded, false if the required assembly was missing. - private static bool TryLoad(AssemblyName assembly) + // Some Melons are missing dependencies. Don't load these Melons and show an error message + if (melonsWithMissingDeps.Count > 0) + MelonLogger.Warning(BuildMissingDependencyMessage(melonsWithMissingDeps)); + + if (melonsWithIncompatibilities.Count > 0) + MelonLogger.Warning(BuildIncompatibleAssembliesMessage(melonsWithIncompatibilities)); + } + + // Returns true if 'assembly' was already loaded or could be loaded, false if the required assembly was missing. + private static bool TryLoad(AssemblyName assembly) + { + try { - try - { #if NET6_0_OR_GREATER - var asm = AssemblyLoadContext.Default.LoadFromAssemblyName(assembly); + var asm = AssemblyLoadContext.Default.LoadFromAssemblyName(assembly); #else - var asm = Assembly.Load(assembly); + var asm = Assembly.Load(assembly); #endif - if (asm == null) - return false; - return true; - } - catch (FileNotFoundException) { return false; } - catch (Exception ex) - { - MelonLogger.Error("Loading Melon Dependency Failed: " + ex); - return false; - } + return asm != null; } - - // Returns true if 'assembly' was already resolved or could be resolved, false if the required assembly was missing. - private static bool TryResolve(AssemblyName assembly) + catch (FileNotFoundException) { - try - { - Assembly asm = SearchDirectoryManager.Scan(assembly.Name); - if (asm == null) - return false; - return true; - } - catch (FileNotFoundException) { return false; } - catch (Exception ex) - { - MelonLogger.Error("Resolving Melon Dependency Failed: " + ex); - return false; - } + return false; } - - private static string BuildMissingDependencyMessage(IDictionary> melonsWithMissingDeps) + catch (Exception ex) { - StringBuilder messageBuilder = new StringBuilder("Some Melons are missing dependencies, which you may have to install.\n" + - "If these are optional dependencies, mark them as optional using the MelonOptionalDependencies attribute.\n" + - "This warning will turn into an error and Melons with missing dependencies will not be loaded in future versions of MelonLoader.\n"); - foreach (string melonName in melonsWithMissingDeps.Keys) - { - messageBuilder.Append($"- '{melonName}' is missing the following dependencies:\n"); - foreach (AssemblyName dependency in melonsWithMissingDeps[melonName]) - messageBuilder.Append($" - '{dependency.Name}' v{dependency.Version}\n"); - } - messageBuilder.Length -= 1; // Remove trailing newline - return messageBuilder.ToString(); + MelonLogger.Error("Loading Melon Dependency Failed: " + ex); + return false; } + } - private static string BuildIncompatibleAssembliesMessage(IDictionary> melonsWithIncompatibilities) + // Returns true if 'assembly' was already resolved or could be resolved, false if the required assembly was missing. + private static bool TryResolve(AssemblyName assembly) + { + try { - StringBuilder messageBuilder = new StringBuilder("Some Melons are marked as incompatible with each other.\n" + - "To avoid any errors, these Melons will not be loaded.\n"); - foreach (string melonName in melonsWithIncompatibilities.Keys) - { - messageBuilder.Append($"- '{melonName}' is incompatible with the following Melons:\n"); - foreach (AssemblyName dependency in melonsWithIncompatibilities[melonName]) - { - messageBuilder.Append($" - '{dependency.Name}'\n"); - } - } - messageBuilder.Length -= 1; // Remove trailing newline - return messageBuilder.ToString(); + var asm = SearchDirectoryManager.Scan(assembly.Name); + return asm != null; } + catch (FileNotFoundException) + { + return false; + } + catch (Exception ex) + { + MelonLogger.Error("Resolving Melon Dependency Failed: " + ex); + return false; + } + } - private void TopologicalSortInto(IList loadedMelons) + private static string BuildMissingDependencyMessage(IDictionary> melonsWithMissingDeps) + { + var messageBuilder = new StringBuilder("Some Melons are missing dependencies, which you may have to install.\n" + + "If these are optional dependencies, mark them as optional using the MelonOptionalDependencies attribute.\n" + + "This warning will turn into an error and Melons with missing dependencies will not be loaded in future versions of MelonLoader.\n"); + foreach (var melonName in melonsWithMissingDeps.Keys) { - int[] unloadedDependencies = new int[vertices.Length]; - SortedList loadableMelons = new SortedList(); - int skippedMelons = 0; + messageBuilder.Append($"- '{melonName}' is missing the following dependencies:\n"); + foreach (var dependency in melonsWithMissingDeps[melonName]) + messageBuilder.Append($" - '{dependency.Name}' v{dependency.Version}\n"); + } + messageBuilder.Length -= 1; // Remove trailing newline + return messageBuilder.ToString(); + } - // Find all sinks in the dependency graph, i.e. Melons without any dependencies on other Melons - for (int i = 0; i < vertices.Length; ++i) + private static string BuildIncompatibleAssembliesMessage(IDictionary> melonsWithIncompatibilities) + { + var messageBuilder = new StringBuilder("Some Melons are marked as incompatible with each other.\n" + + "To avoid any errors, these Melons will not be loaded.\n"); + foreach (var melonName in melonsWithIncompatibilities.Keys) + { + messageBuilder.Append($"- '{melonName}' is incompatible with the following Melons:\n"); + foreach (var dependency in melonsWithIncompatibilities[melonName]) { - Vertex vertex = vertices[i]; - int dependencyCount = vertex.dependencies.Count; - - unloadedDependencies[i] = dependencyCount; - if ((dependencyCount == 0) - && !loadableMelons.ContainsKey(vertex.name)) - loadableMelons.Add(vertex.name, vertex); + messageBuilder.Append($" - '{dependency.Name}'\n"); } + } + messageBuilder.Length -= 1; // Remove trailing newline + return messageBuilder.ToString(); + } - // Perform the (reverse) topological sorting - while (loadableMelons.Count > 0) - { - Vertex melon = loadableMelons.Values[0]; - loadableMelons.RemoveAt(0); + private void TopologicalSortInto(IList loadedMelons) + { + var unloadedDependencies = new int[vertices.Length]; + SortedList loadableMelons = []; + var skippedMelons = 0; - if (!melon.skipLoading - && !loadableMelons.ContainsKey(melon.name)) - loadedMelons.Add(melon.melon); - else - ++skippedMelons; + // Find all sinks in the dependency graph, i.e. Melons without any dependencies on other Melons + for (var i = 0; i < vertices.Length; ++i) + { + var vertex = vertices[i]; + var dependencyCount = vertex.dependencies.Count; - foreach (Vertex dependent in melon.dependents) - { - unloadedDependencies[dependent.index] -= 1; - dependent.skipLoading |= melon.skipLoading; + unloadedDependencies[i] = dependencyCount; + if ((dependencyCount == 0) + && !loadableMelons.ContainsKey(vertex.name)) + loadableMelons.Add(vertex.name, vertex); + } - if ((unloadedDependencies[dependent.index] == 0) - && !loadableMelons.ContainsKey(dependent.name)) - loadableMelons.Add(dependent.name, dependent); - } - } + // Perform the (reverse) topological sorting + while (loadableMelons.Count > 0) + { + var melon = loadableMelons.Values[0]; + loadableMelons.RemoveAt(0); + + if (!melon.skipLoading + && !loadableMelons.ContainsKey(melon.name)) + loadedMelons.Add(melon.melon); + else + ++skippedMelons; - // Check if all Melons were either loaded or skipped. If this is not the case, there is a cycle in the dependency graph - if (loadedMelons.Count + skippedMelons < vertices.Length) + foreach (var dependent in melon.dependents) { - StringBuilder errorMessage = new StringBuilder("Some Melons could not be loaded due to a cyclic dependency:\n"); - for (int i = 0; i < vertices.Length; ++i) - if (unloadedDependencies[i] > 0) - errorMessage.Append($"- '{vertices[i].name}'\n"); - errorMessage.Length -= 1; // Remove trailing newline - MelonLogger.Error(errorMessage.ToString()); + unloadedDependencies[dependent.index] -= 1; + dependent.skipLoading |= melon.skipLoading; + + if ((unloadedDependencies[dependent.index] == 0) + && !loadableMelons.ContainsKey(dependent.name)) + loadableMelons.Add(dependent.name, dependent); } } - private class Vertex + // Check if all Melons were either loaded or skipped. If this is not the case, there is a cycle in the dependency graph + if (loadedMelons.Count + skippedMelons < vertices.Length) { - internal readonly int index; - internal readonly T melon; - internal readonly string name; + var errorMessage = new StringBuilder("Some Melons could not be loaded due to a cyclic dependency:\n"); + for (var i = 0; i < vertices.Length; ++i) + if (unloadedDependencies[i] > 0) + errorMessage.Append($"- '{vertices[i].name}'\n"); + errorMessage.Length -= 1; // Remove trailing newline + MelonLogger.Error(errorMessage.ToString()); + } + } - internal readonly List dependencies; - internal readonly List dependents; - internal bool skipLoading; + private class Vertex + { + internal readonly int index; + internal readonly T melon; + internal readonly string name; - internal Vertex(int index, T melon, string name) - { - this.index = index; - this.melon = melon; - this.name = name; + internal readonly List dependencies; + internal readonly List dependents; + internal bool skipLoading; - dependencies = new List(); - dependents = new List(); - skipLoading = false; - } + internal Vertex(int index, T melon, string name) + { + this.index = index; + this.melon = melon; + this.name = name; + + dependencies = []; + dependents = []; + skipLoading = false; } } } \ No newline at end of file diff --git a/MelonLoader/InternalUtils/MelonStartScreen.cs b/MelonLoader/InternalUtils/MelonStartScreen.cs index 93978780a..886d51ba5 100644 --- a/MelonLoader/InternalUtils/MelonStartScreen.cs +++ b/MelonLoader/InternalUtils/MelonStartScreen.cs @@ -3,27 +3,26 @@ //using MelonLoader.Modules; //using MelonLoader.Utils; -namespace MelonLoader.InternalUtils +namespace MelonLoader.InternalUtils; + +internal class MelonStartScreen { - internal class MelonStartScreen - { - // Doesn't support Unity versions lower than 2017.2.0 (yet?) - // Doesn't support Unity versions lower than 2018 (Crashing Issue) - // Doesn't support Unity versions higher than to 2020.3.21 (Crashing Issue) - //internal static readonly MelonModule.Info moduleInfo = new MelonModule.Info($"{MelonEnvironment.OurRuntimeDirectory}{Path.DirectorySeparatorChar}MelonStartScreen.dll" - // , () => !MelonLaunchOptions.Core.StartScreen || UnityInformationHandler.EngineVersion < new UnityVersion(2018)); + // Doesn't support Unity versions lower than 2017.2.0 (yet?) + // Doesn't support Unity versions lower than 2018 (Crashing Issue) + // Doesn't support Unity versions higher than to 2020.3.21 (Crashing Issue) + //internal static readonly MelonModule.Info moduleInfo = new MelonModule.Info($"{MelonEnvironment.OurRuntimeDirectory}{Path.DirectorySeparatorChar}MelonStartScreen.dll" + // , () => !MelonLaunchOptions.Core.StartScreen || UnityInformationHandler.EngineVersion < new UnityVersion(2018)); - internal static int LoadAndRun(LemonFunc functionToWaitForAsync) - { - //var module = MelonModule.Load(moduleInfo); - //if (module == null) - return functionToWaitForAsync(); + internal static int LoadAndRun(LemonFunc functionToWaitForAsync) + { + //var module = MelonModule.Load(moduleInfo); + //if (module == null) + return functionToWaitForAsync(); - //var result = module.SendMessage("LoadAndRun", functionToWaitForAsync); - //if (result is int resultCode) - // return resultCode; + //var result = module.SendMessage("LoadAndRun", functionToWaitForAsync); + //if (result is int resultCode) + // return resultCode; - //return -1; - } + //return -1; } } diff --git a/MelonLoader/InternalUtils/UnityInformationHandler.cs b/MelonLoader/InternalUtils/UnityInformationHandler.cs index 3578b5fbb..b4a4c9abf 100644 --- a/MelonLoader/InternalUtils/UnityInformationHandler.cs +++ b/MelonLoader/InternalUtils/UnityInformationHandler.cs @@ -1,252 +1,252 @@ -using System; +using AssetsTools.NET; +using AssetsTools.NET.Extra; +using MelonLoader.Utils; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using AssetsTools.NET; -using AssetsTools.NET.Extra; -using System.Drawing; -using MelonLoader.Utils; using UnityVersion = AssetRipper.VersionUtilities.UnityVersion; -namespace MelonLoader.InternalUtils +namespace MelonLoader.InternalUtils; + +public static class UnityInformationHandler { - public static class UnityInformationHandler - { - private const string DefaultInfo = "UNKNOWN"; + private const string DefaultInfo = "UNKNOWN"; + + public static string GameName { get; private set; } + public static string GameDeveloper { get; private set; } + public static UnityVersion EngineVersion { get; private set; } + public static string GameVersion { get; private set; } - public static string GameName { get; private set; } - public static string GameDeveloper { get; private set; } - public static UnityVersion EngineVersion { get; private set; } - public static string GameVersion { get; private set; } + private static UnityVersion TryParse(string version) + { + _ = UnityVersion.MinVersion; - private static UnityVersion TryParse(string version) + UnityVersion returnval; + try { - UnityVersion returnval = UnityVersion.MinVersion; - try - { - returnval = UnityVersion.Parse(version); - } - catch (Exception ex) - { - if (MelonDebug.IsEnabled()) - MelonLogger.Error(ex); - returnval = UnityVersion.MinVersion; - } - return returnval; + returnval = UnityVersion.Parse(version); + } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + returnval = UnityVersion.MinVersion; } + return returnval; + } - internal static void Setup() + internal static void Setup() + { + var gameDataPath = MelonEnvironment.UnityGameDataDirectory; + + if (!string.IsNullOrEmpty(LoaderConfig.Current.UnityEngine.VersionOverride)) + EngineVersion = TryParse(LoaderConfig.Current.UnityEngine.VersionOverride); + + var assetsManager = new AssetsManager(); + ReadGameInfo(assetsManager, gameDataPath); + assetsManager.UnloadAll(); + + if (string.IsNullOrEmpty(GameDeveloper) + || string.IsNullOrEmpty(GameName)) + ReadGameInfoFallback(); + + if (EngineVersion == UnityVersion.MinVersion) + EngineVersion = ReadVersionFallback(gameDataPath); + + if (string.IsNullOrEmpty(GameDeveloper)) + GameDeveloper = DefaultInfo; + if (string.IsNullOrEmpty(GameName)) + GameName = DefaultInfo; + if (string.IsNullOrEmpty(GameVersion)) + GameVersion = DefaultInfo; + + MelonLogger.WriteLine(Color.Magenta); + MelonLogger.Msg($"Game Name: {GameName}"); + MelonLogger.Msg($"Game Developer: {GameDeveloper}"); + MelonLogger.Msg($"Unity Version: {EngineVersion}"); + MelonLogger.Msg($"Game Version: {GameVersion}"); + MelonLogger.WriteLine(Color.Magenta); + MelonLogger.WriteSpacer(); + } + + private static void ReadGameInfo(AssetsManager assetsManager, string gameDataPath) + { + AssetsFileInstance instance = null; + try { - string gameDataPath = MelonEnvironment.UnityGameDataDirectory; + var bundlePath = Path.Combine(gameDataPath, "globalgamemanagers"); + if (!File.Exists(bundlePath)) + bundlePath = Path.Combine(gameDataPath, "mainData"); - if (!string.IsNullOrEmpty(LoaderConfig.Current.UnityEngine.VersionOverride)) - EngineVersion = TryParse(LoaderConfig.Current.UnityEngine.VersionOverride); + if (!File.Exists(bundlePath)) + { + bundlePath = Path.Combine(gameDataPath, "data.unity3d"); + if (!File.Exists(bundlePath)) + return; - AssetsManager assetsManager = new AssetsManager(); - ReadGameInfo(assetsManager, gameDataPath); - assetsManager.UnloadAll(); + var bundleFile = assetsManager.LoadBundleFile(bundlePath); + instance = assetsManager.LoadAssetsFileFromBundle(bundleFile, "globalgamemanagers"); + } + else + instance = assetsManager.LoadAssetsFile(bundlePath, true); + if (instance == null) + return; - if (string.IsNullOrEmpty(GameDeveloper) - || string.IsNullOrEmpty(GameName)) - ReadGameInfoFallback(); + assetsManager.LoadIncludedClassPackage(); + if (!instance.file.Metadata.TypeTreeEnabled) + assetsManager.LoadClassDatabaseFromPackage(instance.file.Metadata.UnityVersion); if (EngineVersion == UnityVersion.MinVersion) - EngineVersion = ReadVersionFallback(gameDataPath); - - if (string.IsNullOrEmpty(GameDeveloper)) - GameDeveloper = DefaultInfo; - if (string.IsNullOrEmpty(GameName)) - GameName = DefaultInfo; - if (string.IsNullOrEmpty(GameVersion)) - GameVersion = DefaultInfo; - - MelonLogger.WriteLine(Color.Magenta); - MelonLogger.Msg($"Game Name: {GameName}"); - MelonLogger.Msg($"Game Developer: {GameDeveloper}"); - MelonLogger.Msg($"Unity Version: {EngineVersion}"); - MelonLogger.Msg($"Game Version: {GameVersion}"); - MelonLogger.WriteLine(Color.Magenta); - MelonLogger.WriteSpacer(); - } + EngineVersion = TryParse(instance.file.Metadata.UnityVersion); - private static void ReadGameInfo(AssetsManager assetsManager, string gameDataPath) - { - AssetsFileInstance instance = null; - try + var assetFiles = instance.file.GetAssetsOfType(AssetClassID.PlayerSettings); + if (assetFiles.Count > 0) { - string bundlePath = Path.Combine(gameDataPath, "globalgamemanagers"); - if (!File.Exists(bundlePath)) - bundlePath = Path.Combine(gameDataPath, "mainData"); + var playerSettings = assetFiles.First(); - if (!File.Exists(bundlePath)) + var playerSettings_baseField = assetsManager.GetBaseField(instance, playerSettings); + if (playerSettings_baseField != null) { - bundlePath = Path.Combine(gameDataPath, "data.unity3d"); - if (!File.Exists(bundlePath)) - return; - - BundleFileInstance bundleFile = assetsManager.LoadBundleFile(bundlePath); - instance = assetsManager.LoadAssetsFileFromBundle(bundleFile, "globalgamemanagers"); - } - else - instance = assetsManager.LoadAssetsFile(bundlePath, true); - if (instance == null) - return; - - assetsManager.LoadIncludedClassPackage(); - if (!instance.file.Metadata.TypeTreeEnabled) - assetsManager.LoadClassDatabaseFromPackage(instance.file.Metadata.UnityVersion); + var bundleVersion = playerSettings_baseField.Get("bundleVersion"); + if (bundleVersion != null) + GameVersion = bundleVersion.AsString; - if (EngineVersion == UnityVersion.MinVersion) - EngineVersion = TryParse(instance.file.Metadata.UnityVersion); + var companyName = playerSettings_baseField.Get("companyName"); + if (companyName != null) + GameDeveloper = companyName.AsString; - List assetFiles = instance.file.GetAssetsOfType(AssetClassID.PlayerSettings); - if (assetFiles.Count > 0) - { - AssetFileInfo playerSettings = assetFiles.First(); - - AssetTypeValueField playerSettings_baseField = assetsManager.GetBaseField(instance, playerSettings); - if (playerSettings_baseField != null) - { - AssetTypeValueField bundleVersion = playerSettings_baseField.Get("bundleVersion"); - if (bundleVersion != null) - GameVersion = bundleVersion.AsString; - - AssetTypeValueField companyName = playerSettings_baseField.Get("companyName"); - if (companyName != null) - GameDeveloper = companyName.AsString; - - AssetTypeValueField productName = playerSettings_baseField.Get("productName"); - if (productName != null) - GameName = productName.AsString; - } + var productName = playerSettings_baseField.Get("productName"); + if (productName != null) + GameName = productName.AsString; } } - catch(Exception ex) - { - if (MelonDebug.IsEnabled()) - MelonLogger.Error(ex); - } - if (instance != null) - instance.file.Close(); } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } + instance?.file.Close(); + } - private static void ReadGameInfoFallback() + private static void ReadGameInfoFallback() + { + try { - try - { - string appInfoFilePath = Path.Combine(MelonEnvironment.UnityGameDataDirectory, "app.info"); - if (!File.Exists(appInfoFilePath)) - return; + var appInfoFilePath = Path.Combine(MelonEnvironment.UnityGameDataDirectory, "app.info"); + if (!File.Exists(appInfoFilePath)) + return; - string[] filestr = File.ReadAllLines(appInfoFilePath); - if ((filestr == null) || (filestr.Length < 2)) - return; + var filestr = File.ReadAllLines(appInfoFilePath); + if ((filestr == null) || (filestr.Length < 2)) + return; - if (string.IsNullOrEmpty(GameDeveloper) && !string.IsNullOrEmpty(filestr[0])) - GameDeveloper = filestr[0]; + if (string.IsNullOrEmpty(GameDeveloper) && !string.IsNullOrEmpty(filestr[0])) + GameDeveloper = filestr[0]; - if (string.IsNullOrEmpty(GameName) && !string.IsNullOrEmpty(filestr[1])) - GameName = filestr[1]; + if (string.IsNullOrEmpty(GameName) && !string.IsNullOrEmpty(filestr[1])) + GameName = filestr[1]; - } - catch (Exception ex) - { - if (MelonDebug.IsEnabled()) - MelonLogger.Error(ex); - } } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } + } + + private static UnityVersion ReadVersionFallback(string gameDataPath) + { + var unityPlayerPath = MelonEnvironment.UnityPlayerPath; + if (!File.Exists(unityPlayerPath)) + unityPlayerPath = MelonEnvironment.GameExecutablePath; - private static UnityVersion ReadVersionFallback(string gameDataPath) + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - string unityPlayerPath = MelonEnvironment.UnityPlayerPath; - if (!File.Exists(unityPlayerPath)) - unityPlayerPath = MelonEnvironment.GameExecutablePath; + var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath); + return TryParse(unityVer.FileVersion); + } - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath); - return TryParse(unityVer.FileVersion); - } + try + { + var globalgamemanagersPath = Path.Combine(gameDataPath, "globalgamemanagers"); + if (File.Exists(globalgamemanagersPath)) + return GetVersionFromGlobalGameManagers(File.ReadAllBytes(globalgamemanagersPath)); + } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } - try - { - var globalgamemanagersPath = Path.Combine(gameDataPath, "globalgamemanagers"); - if (File.Exists(globalgamemanagersPath)) - return GetVersionFromGlobalGameManagers(File.ReadAllBytes(globalgamemanagersPath)); - } - catch (Exception ex) - { - if (MelonDebug.IsEnabled()) - MelonLogger.Error(ex); - } + try + { + var dataPath = Path.Combine(gameDataPath, "data.unity3d"); + if (File.Exists(dataPath)) + return GetVersionFromDataUnity3D(File.OpenRead(dataPath)); + } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } - try - { - var dataPath = Path.Combine(gameDataPath, "data.unity3d"); - if (File.Exists(dataPath)) - return GetVersionFromDataUnity3D(File.OpenRead(dataPath)); - } - catch (Exception ex) - { - if (MelonDebug.IsEnabled()) - MelonLogger.Error(ex); - } + return UnityVersion.MinVersion; + } - return UnityVersion.MinVersion; + private static UnityVersion GetVersionFromGlobalGameManagers(byte[] ggmBytes) + { + var verString = new StringBuilder(); + var idx = 0x14; + while (ggmBytes[idx] != 0) + { + verString.Append(Convert.ToChar(ggmBytes[idx])); + idx++; } - private static UnityVersion GetVersionFromGlobalGameManagers(byte[] ggmBytes) + var UnityVersionRegex = new Regex(@"^[0-9]+\.[0-9]+\.[0-9]+[abcfx][0-9]+$", RegexOptions.Compiled); + var unityVer = verString.ToString(); + if (!UnityVersionRegex.IsMatch(unityVer)) { - var verString = new StringBuilder(); - var idx = 0x14; + idx = 0x30; + verString = new StringBuilder(); while (ggmBytes[idx] != 0) { verString.Append(Convert.ToChar(ggmBytes[idx])); idx++; } - Regex UnityVersionRegex = new Regex(@"^[0-9]+\.[0-9]+\.[0-9]+[abcfx][0-9]+$", RegexOptions.Compiled); - string unityVer = verString.ToString(); - if (!UnityVersionRegex.IsMatch(unityVer)) - { - idx = 0x30; - verString = new StringBuilder(); - while (ggmBytes[idx] != 0) - { - verString.Append(Convert.ToChar(ggmBytes[idx])); - idx++; - } - - unityVer = verString.ToString().Trim(); - } - - return TryParse(unityVer); + unityVer = verString.ToString().Trim(); } - private static UnityVersion GetVersionFromDataUnity3D(Stream fileStream) - { - var verString = new StringBuilder(); + return TryParse(unityVer); + } - if (fileStream.CanSeek) - fileStream.Seek(0x12, SeekOrigin.Begin); - else - { - if (fileStream.Read(new byte[0x12], 0, 0x12) != 0x12) - throw new("Failed to seek to 0x12 in data.unity3d"); - } + private static UnityVersion GetVersionFromDataUnity3D(Stream fileStream) + { + var verString = new StringBuilder(); - while (true) - { - var read = fileStream.ReadByte(); - if (read == 0) - break; - verString.Append(Convert.ToChar(read)); - } + if (fileStream.CanSeek) + fileStream.Seek(0x12, SeekOrigin.Begin); + else + { + if (fileStream.Read(new byte[0x12], 0, 0x12) != 0x12) + throw new("Failed to seek to 0x12 in data.unity3d"); + } - return TryParse(verString.ToString().Trim()); + while (true) + { + var read = fileStream.ReadByte(); + if (read == 0) + break; + verString.Append(Convert.ToChar(read)); } + + return TryParse(verString.ToString().Trim()); } } diff --git a/MelonLoader/InternalUtils/UnityMagicMethods.cs b/MelonLoader/InternalUtils/UnityMagicMethods.cs index 1f485139b..7ae955a51 100644 --- a/MelonLoader/InternalUtils/UnityMagicMethods.cs +++ b/MelonLoader/InternalUtils/UnityMagicMethods.cs @@ -2,152 +2,151 @@ using System.Collections.Generic; using System.Reflection; -namespace MelonLoader.InternalUtils +namespace MelonLoader.InternalUtils; + +/* + * A list of Unity's built-in messages / magic methods and their (optional) argument types. + * Used to prevent false-positive "IL2CPP method got inlined, patch may not work" warnings. + * Put in its own static class to prevent unnecessary initialization for Mono games. + */ +public static class UnityMagicMethods { - /* - * A list of Unity's built-in messages / magic methods and their (optional) argument types. - * Used to prevent false-positive "IL2CPP method got inlined, patch may not work" warnings. - * Put in its own static class to prevent unnecessary initialization for Mono games. - */ - public static class UnityMagicMethods - { - private static Type MonoBehaviourType = null; - private static Type ScriptableObjectType = null; + private static readonly Type MonoBehaviourType = null; + private static readonly Type ScriptableObjectType = null; + + // Key = name of magic method. Value = optional argument types + // See: https://docs.unity3d.com/ScriptReference/MonoBehaviour.html + private static readonly Dictionary MonoBehaviourMethods = null; + // See: https://docs.unity3d.com/ScriptReference/ScriptableObject.html + private static readonly Dictionary ScriptableObjectMethods = null; - // Key = name of magic method. Value = optional argument types - // See: https://docs.unity3d.com/ScriptReference/MonoBehaviour.html - private static Dictionary MonoBehaviourMethods = null; - // See: https://docs.unity3d.com/ScriptReference/ScriptableObject.html - private static Dictionary ScriptableObjectMethods = null; + static UnityMagicMethods() + { + var unityAssembly = Assembly.Load("UnityEngine.CoreModule") ?? Assembly.Load("UnityEngine"); + Type unityType(string name) // This may return null - especially for types such as NetworkPlayer that have been removed in newer Unity versions + => unityAssembly.GetType(name); + + MonoBehaviourType = unityType("UnityEngine.MonoBehaviour"); + ScriptableObjectType = unityType("UnityEngine.ScriptableObject"); - static UnityMagicMethods() + MonoBehaviourMethods = new Dictionary { - Assembly unityAssembly = Assembly.Load("UnityEngine.CoreModule") ?? Assembly.Load("UnityEngine"); - Type unityType(string name) // This may return null - especially for types such as NetworkPlayer that have been removed in newer Unity versions - => unityAssembly.GetType(name); - - MonoBehaviourType = unityType("UnityEngine.MonoBehaviour"); - ScriptableObjectType = unityType("UnityEngine.ScriptableObject"); - - MonoBehaviourMethods = new Dictionary - { - ["Awake"] = new Type[0], - ["FixedUpdate"] = new Type[0], - ["LateUpdate"] = new Type[0], - ["OnAnimatorIK"] = new[] { typeof(int) }, - ["OnAnimatorMove"] = new Type[0], - ["OnApplicationFocus"] = new[] { typeof(bool) }, - ["OnApplicationPause"] = new[] { typeof(bool) }, - ["OnApplicationQuit"] = new Type[0], - ["OnAudioFilterRead"] = new[] { typeof(float[]), typeof(int) }, - ["OnBecameInvisible"] = new Type[0], - ["OnBecameVisible"] = new Type[0], - ["OnCollisionEnter"] = new[] { unityType("UnityEngine.Collision") }, - ["OnCollisionEnter2D"] = new[] { unityType("UnityEngine.Collision2D") }, - ["OnCollisionExit"] = new[] { unityType("UnityEngine.Collision") }, - ["OnCollisionExit2D"] = new[] { unityType("UnityEngine.Collision2D") }, - ["OnCollisionStay"] = new[] { unityType("UnityEngine.Collision") }, - ["OnCollisionStay2D"] = new[] { unityType("UnityEngine.Collision2D") }, - ["OnConnectedToServer"] = new Type[0], - ["OnControllerColliderHit"] = new[] { unityType("UnityEngine.ControllerColliderHit") }, - ["OnDestroy"] = new Type[0], - ["OnDisable"] = new Type[0], - ["OnDisconnectedFromServer"] = new[] { unityType("UnityEngine.NetworkDisconnection") }, - // "OnDrawGizmos" is editor-only - // "OnDrawGizmosSelected" is editor-only - ["OnEnable"] = new Type[0], - ["OnFailedToConnect"] = new[] { unityType("UnityEngine.NetworkConnectionError") }, - ["OnFailedToConnectToMasterServer"] = new[] { unityType("UnityEngine.NetworkConnectionError") }, - ["OnGUI"] = new Type[0], - ["OnJointBreak"] = new[] { typeof(float) }, - ["OnJointBreak2D"] = new[] { unityType("UnityEngine.Joint2D") }, - ["OnMasterServerEvent"] = new[] { unityType("UnityEngine.MasterServerEvent") }, - ["OnMouseDown"] = new Type[0], - ["OnMouseDrag"] = new Type[0], - ["OnMouseEnter"] = new Type[0], - ["OnMouseExit"] = new Type[0], - ["OnMouseOver"] = new Type[0], - ["OnMouseUp"] = new Type[0], - ["OnMouseUpAsButton"] = new Type[0], - ["OnNetworkInstantiate"] = new[] { unityType("UnityEngine.NetworkMessageInfo") }, - ["OnParticleCollision"] = new[] { unityType("UnityEngine.GameObject") }, - ["OnParticleSystemStopped"] = new Type[0], - ["OnParticleTrigger"] = new Type[0], - ["OnParticleUpdateJobScheduled"] = new Type[0], - ["OnPlayerConnected"] = new[] { unityType("UnityEngine.NetworkPlayer") }, - ["OnPlayerDisconnected"] = new[] { unityType("UnityEngine.NetworkPlayer") }, - ["OnPostRender"] = new Type[0], - ["OnPreCull"] = new Type[0], - ["OnPreRender"] = new Type[0], - ["OnRenderImage"] = new[] { unityType("UnityEngine.RenderTexture"), unityType("UnityEngine.RenderTexture") }, - ["OnRenderObject"] = new Type[0], - ["OnSerializeNetworkView"] = new[] { unityType("UnityEngine.BitStream"), unityType("UnityEngine.NetworkMessageInfo") }, - ["OnServerInitialized"] = new Type[0], - ["OnTransformChildrenChanged"] = new Type[0], - ["OnTransformParentChanged"] = new Type[0], - ["OnTriggerEnter"] = new[] { unityType("UnityEngine.Collider") }, - ["OnTriggerEnter2D"] = new[] { unityType("UnityEngine.Collider2D") }, - ["OnTriggerExit"] = new[] { unityType("UnityEngine.Collider") }, - ["OnTriggerExit2D"] = new[] { unityType("UnityEngine.Collider2D") }, - ["OnTriggerStay"] = new[] { unityType("UnityEngine.Collider") }, - ["OnTriggerStay2D"] = new[] { unityType("UnityEngine.Collider2D") }, - // "OnValidate" is editor-only - ["OnWillRenderObject"] = new Type[0], - // "Reset" is editor-only - ["Start"] = new Type[0], - ["Update"] = new Type[0], - }; - - ScriptableObjectMethods = new Dictionary - { - ["Awake"] = new Type[0], - ["OnDestroy"] = new Type[0], - ["OnDisable"] = new Type[0], - ["OnEnable"] = new Type[0], - }; - } + ["Awake"] = new Type[0], + ["FixedUpdate"] = new Type[0], + ["LateUpdate"] = new Type[0], + ["OnAnimatorIK"] = new[] { typeof(int) }, + ["OnAnimatorMove"] = new Type[0], + ["OnApplicationFocus"] = new[] { typeof(bool) }, + ["OnApplicationPause"] = new[] { typeof(bool) }, + ["OnApplicationQuit"] = new Type[0], + ["OnAudioFilterRead"] = new[] { typeof(float[]), typeof(int) }, + ["OnBecameInvisible"] = new Type[0], + ["OnBecameVisible"] = new Type[0], + ["OnCollisionEnter"] = new[] { unityType("UnityEngine.Collision") }, + ["OnCollisionEnter2D"] = new[] { unityType("UnityEngine.Collision2D") }, + ["OnCollisionExit"] = new[] { unityType("UnityEngine.Collision") }, + ["OnCollisionExit2D"] = new[] { unityType("UnityEngine.Collision2D") }, + ["OnCollisionStay"] = new[] { unityType("UnityEngine.Collision") }, + ["OnCollisionStay2D"] = new[] { unityType("UnityEngine.Collision2D") }, + ["OnConnectedToServer"] = new Type[0], + ["OnControllerColliderHit"] = new[] { unityType("UnityEngine.ControllerColliderHit") }, + ["OnDestroy"] = new Type[0], + ["OnDisable"] = new Type[0], + ["OnDisconnectedFromServer"] = new[] { unityType("UnityEngine.NetworkDisconnection") }, + // "OnDrawGizmos" is editor-only + // "OnDrawGizmosSelected" is editor-only + ["OnEnable"] = new Type[0], + ["OnFailedToConnect"] = new[] { unityType("UnityEngine.NetworkConnectionError") }, + ["OnFailedToConnectToMasterServer"] = new[] { unityType("UnityEngine.NetworkConnectionError") }, + ["OnGUI"] = new Type[0], + ["OnJointBreak"] = new[] { typeof(float) }, + ["OnJointBreak2D"] = new[] { unityType("UnityEngine.Joint2D") }, + ["OnMasterServerEvent"] = new[] { unityType("UnityEngine.MasterServerEvent") }, + ["OnMouseDown"] = new Type[0], + ["OnMouseDrag"] = new Type[0], + ["OnMouseEnter"] = new Type[0], + ["OnMouseExit"] = new Type[0], + ["OnMouseOver"] = new Type[0], + ["OnMouseUp"] = new Type[0], + ["OnMouseUpAsButton"] = new Type[0], + ["OnNetworkInstantiate"] = new[] { unityType("UnityEngine.NetworkMessageInfo") }, + ["OnParticleCollision"] = new[] { unityType("UnityEngine.GameObject") }, + ["OnParticleSystemStopped"] = new Type[0], + ["OnParticleTrigger"] = new Type[0], + ["OnParticleUpdateJobScheduled"] = new Type[0], + ["OnPlayerConnected"] = new[] { unityType("UnityEngine.NetworkPlayer") }, + ["OnPlayerDisconnected"] = new[] { unityType("UnityEngine.NetworkPlayer") }, + ["OnPostRender"] = new Type[0], + ["OnPreCull"] = new Type[0], + ["OnPreRender"] = new Type[0], + ["OnRenderImage"] = new[] { unityType("UnityEngine.RenderTexture"), unityType("UnityEngine.RenderTexture") }, + ["OnRenderObject"] = new Type[0], + ["OnSerializeNetworkView"] = new[] { unityType("UnityEngine.BitStream"), unityType("UnityEngine.NetworkMessageInfo") }, + ["OnServerInitialized"] = new Type[0], + ["OnTransformChildrenChanged"] = new Type[0], + ["OnTransformParentChanged"] = new Type[0], + ["OnTriggerEnter"] = new[] { unityType("UnityEngine.Collider") }, + ["OnTriggerEnter2D"] = new[] { unityType("UnityEngine.Collider2D") }, + ["OnTriggerExit"] = new[] { unityType("UnityEngine.Collider") }, + ["OnTriggerExit2D"] = new[] { unityType("UnityEngine.Collider2D") }, + ["OnTriggerStay"] = new[] { unityType("UnityEngine.Collider") }, + ["OnTriggerStay2D"] = new[] { unityType("UnityEngine.Collider2D") }, + // "OnValidate" is editor-only + ["OnWillRenderObject"] = new Type[0], + // "Reset" is editor-only + ["Start"] = new Type[0], + ["Update"] = new Type[0], + }; - public static bool IsUnityMagicMethod(MethodBase method) + ScriptableObjectMethods = new Dictionary { - if (method == null - || method.IsStatic - || method.IsAbstract - || method.IsGenericMethod - || method.IsConstructor) - return false; + ["Awake"] = new Type[0], + ["OnDestroy"] = new Type[0], + ["OnDisable"] = new Type[0], + ["OnEnable"] = new Type[0], + }; + } - Type owner = method.DeclaringType; - if (owner.IsSubclassOf(MonoBehaviourType)) - return CheckMagicMethod(method, MonoBehaviourMethods); - else if (owner.IsSubclassOf(ScriptableObjectType)) - return CheckMagicMethod(method, ScriptableObjectMethods); + public static bool IsUnityMagicMethod(MethodBase method) + { + if (method == null + || method.IsStatic + || method.IsAbstract + || method.IsGenericMethod + || method.IsConstructor) return false; - } - - private static bool CheckMagicMethod(MethodBase method, Dictionary magicMethods) - { - if (!magicMethods.TryGetValue(method.Name, out Type[] magicArgTypes)) - return false; - ParameterInfo[] parameters = method.GetParameters(); + var owner = method.DeclaringType; + if (owner.IsSubclassOf(MonoBehaviourType)) + return CheckMagicMethod(method, MonoBehaviourMethods); + else if (owner.IsSubclassOf(ScriptableObjectType)) + return CheckMagicMethod(method, ScriptableObjectMethods); + return false; + } - // No-args method with the correct name and correct owner -> magic method - // If there are arguments, all of their types have to match the magic method template. This may be too restrictive. + private static bool CheckMagicMethod(MethodBase method, Dictionary magicMethods) + { + if (!magicMethods.TryGetValue(method.Name, out var magicArgTypes)) + return false; - if (parameters.Length == 0) - return true; - else if (parameters.Length != magicArgTypes.Length) - return false; + var parameters = method.GetParameters(); - for (int i = 0; i < parameters.Length; ++i) - { - if (magicArgTypes[i] == null - || magicArgTypes[i] != parameters[i].ParameterType) - return false; - } + // No-args method with the correct name and correct owner -> magic method + // If there are arguments, all of their types have to match the magic method template. This may be too restrictive. + if (parameters.Length == 0) return true; + else if (parameters.Length != magicArgTypes.Length) + return false; + + for (var i = 0; i < parameters.Length; ++i) + { + if (magicArgTypes[i] == null + || magicArgTypes[i] != parameters[i].ParameterType) + return false; } + + return true; } } \ No newline at end of file diff --git a/MelonLoader/InteropSupport.cs b/MelonLoader/InteropSupport.cs index dd11bab9e..5361d9fbd 100644 --- a/MelonLoader/InteropSupport.cs +++ b/MelonLoader/InteropSupport.cs @@ -1,125 +1,118 @@ using System; using System.Reflection; -namespace MelonLoader +namespace MelonLoader; + +public static class InteropSupport { - public static class InteropSupport + public interface Interface + { + bool IsInheritedFromIl2CppObjectBase(Type type); + public bool IsInjectedType(Type type); + public IntPtr GetClassPointerForType(Type type); + FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method); + int? GetIl2CppMethodCallerCount(MethodBase method); + void RegisterTypeInIl2CppDomain(Type type, bool logSuccess); + void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces, bool logSuccess); + IntPtr CopyMethodInfoStruct(IntPtr ptr); + } + internal static Interface SMInterface; + + private static void ValidateInterface() + { + if (!MelonUtils.IsGameIl2Cpp()) + throw new Exception("MelonLoader.InteropSupport can't be used on Non-Il2Cpp Games"); + if (SMInterface == null) + throw new NullReferenceException("SMInterface cannot be null."); + } + + public static bool IsGeneratedAssemblyType(Type type) + => IsInheritedFromIl2CppObjectBase(type) && !IsInjectedType(type); + + public static bool IsInheritedFromIl2CppObjectBase(Type type) + { + ValidateInterface(); + return type == null + ? throw new NullReferenceException("The type cannot be null.") + : SMInterface.IsInheritedFromIl2CppObjectBase(type); + } + + public static bool IsInjectedType(Type type) + { + ValidateInterface(); + return type == null ? throw new NullReferenceException("The type cannot be null.") : SMInterface.IsInjectedType(type); + } + + public static IntPtr GetClassPointerForType(Type type) + { + ValidateInterface(); + return type == null ? throw new NullReferenceException("The type cannot be null.") : SMInterface.GetClassPointerForType(type); + } + + public static IntPtr MethodBaseToIl2CppMethodInfoPointer(MethodBase method) + { + ValidateInterface(); + if (method == null) + throw new NullReferenceException("The method cannot be null."); + var field = MethodBaseToIl2CppFieldInfo(method); + return field == null ? IntPtr.Zero : (IntPtr)field.GetValue(null); + } + + public static FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method) + { + ValidateInterface(); + return method == null + ? throw new NullReferenceException("The method cannot be null.") + : SMInterface.MethodBaseToIl2CppFieldInfo(method); + } + + public static T Il2CppObjectPtrToIl2CppObject(IntPtr ptr) + { + ValidateInterface(); + if (ptr == IntPtr.Zero) + throw new NullReferenceException("The ptr cannot be IntPtr.Zero."); + return !IsGeneratedAssemblyType(typeof(T)) + ? throw new NullReferenceException("The type must be a Generated Assembly Type.") + : (T)typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(IntPtr) }, new ParameterModifier[0]).Invoke(new object[] { ptr }); + } + + public static int? GetIl2CppMethodCallerCount(MethodBase method) + { + ValidateInterface(); + return method == null + ? throw new NullReferenceException("The method cannot be null.") + : SMInterface.GetIl2CppMethodCallerCount(method); + } + + public static void RegisterTypeInIl2CppDomain(Type type) + => RegisterTypeInIl2CppDomain(type, true); + + public static void RegisterTypeInIl2CppDomain(Type type, bool logSuccess) + { + ValidateInterface(); + if (type == null) + throw new NullReferenceException("The type cannot be null."); + SMInterface.RegisterTypeInIl2CppDomain(type, logSuccess); + } + + public static void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces) + => RegisterTypeInIl2CppDomainWithInterfaces(type, interfaces, true); + + public static void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces, bool logSuccess) + { + ValidateInterface(); + if (type == null) + throw new NullReferenceException("The type cannot be null."); + if (interfaces == null) + throw new NullReferenceException("The interfaces cannot be null."); + if (interfaces.Length <= 0) + throw new NullReferenceException("The interfaces cannot be empty."); + SMInterface.RegisterTypeInIl2CppDomainWithInterfaces(type, interfaces, logSuccess); + } + + public static IntPtr CopyMethodInfoStruct(IntPtr ptr) { - public interface Interface - { - bool IsInheritedFromIl2CppObjectBase(Type type); - public bool IsInjectedType(Type type); - public IntPtr GetClassPointerForType(Type type); - FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method); - int? GetIl2CppMethodCallerCount(MethodBase method); - void RegisterTypeInIl2CppDomain(Type type, bool logSuccess); - void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces, bool logSuccess); - IntPtr CopyMethodInfoStruct(IntPtr ptr); - } - internal static Interface SMInterface; - - private static void ValidateInterface() - { - if (!MelonUtils.IsGameIl2Cpp()) - throw new Exception("MelonLoader.InteropSupport can't be used on Non-Il2Cpp Games"); - if (SMInterface == null) - throw new NullReferenceException("SMInterface cannot be null."); - } - - public static bool IsGeneratedAssemblyType(Type type) - => IsInheritedFromIl2CppObjectBase(type) && !IsInjectedType(type); - - public static bool IsInheritedFromIl2CppObjectBase(Type type) - { - ValidateInterface(); - if (type == null) - throw new NullReferenceException("The type cannot be null."); - return SMInterface.IsInheritedFromIl2CppObjectBase(type); - } - - public static bool IsInjectedType(Type type) - { - ValidateInterface(); - if (type == null) - throw new NullReferenceException("The type cannot be null."); - return SMInterface.IsInjectedType(type); - } - - public static IntPtr GetClassPointerForType(Type type) - { - ValidateInterface(); - if (type == null) - throw new NullReferenceException("The type cannot be null."); - return SMInterface.GetClassPointerForType(type); - } - - public static IntPtr MethodBaseToIl2CppMethodInfoPointer(MethodBase method) - { - ValidateInterface(); - if (method == null) - throw new NullReferenceException("The method cannot be null."); - FieldInfo field = MethodBaseToIl2CppFieldInfo(method); - if (field == null) - return IntPtr.Zero; - return (IntPtr)field.GetValue(null); - } - - public static FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method) - { - ValidateInterface(); - if (method == null) - throw new NullReferenceException("The method cannot be null."); - return SMInterface.MethodBaseToIl2CppFieldInfo(method); - } - - public static T Il2CppObjectPtrToIl2CppObject(IntPtr ptr) - { - ValidateInterface(); - if (ptr == IntPtr.Zero) - throw new NullReferenceException("The ptr cannot be IntPtr.Zero."); - if (!IsGeneratedAssemblyType(typeof(T))) - throw new NullReferenceException("The type must be a Generated Assembly Type."); - return (T)typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(IntPtr) }, new ParameterModifier[0]).Invoke(new object[] { ptr }); - } - - public static int? GetIl2CppMethodCallerCount(MethodBase method) - { - ValidateInterface(); - if (method == null) - throw new NullReferenceException("The method cannot be null."); - return SMInterface.GetIl2CppMethodCallerCount(method); - } - - public static void RegisterTypeInIl2CppDomain(Type type) - => RegisterTypeInIl2CppDomain(type, true); - - public static void RegisterTypeInIl2CppDomain(Type type, bool logSuccess) - { - ValidateInterface(); - if (type == null) - throw new NullReferenceException("The type cannot be null."); - SMInterface.RegisterTypeInIl2CppDomain(type, logSuccess); - } - - public static void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces) - => RegisterTypeInIl2CppDomainWithInterfaces(type, interfaces, true); - - public static void RegisterTypeInIl2CppDomainWithInterfaces(Type type, Type[] interfaces, bool logSuccess) - { - ValidateInterface(); - if (type == null) - throw new NullReferenceException("The type cannot be null."); - if (interfaces == null) - throw new NullReferenceException("The interfaces cannot be null."); - if (interfaces.Length <= 0) - throw new NullReferenceException("The interfaces cannot be empty."); - SMInterface.RegisterTypeInIl2CppDomainWithInterfaces(type, interfaces, logSuccess); - } - - public static IntPtr CopyMethodInfoStruct(IntPtr ptr) - { - ValidateInterface(); - return SMInterface.CopyMethodInfoStruct(ptr); - } + ValidateInterface(); + return SMInterface.CopyMethodInfoStruct(ptr); } } \ No newline at end of file diff --git a/MelonLoader/LemonAction.cs b/MelonLoader/LemonAction.cs index 811396ea2..6dfd8cd4e 100644 --- a/MelonLoader/LemonAction.cs +++ b/MelonLoader/LemonAction.cs @@ -1,12 +1,11 @@ -namespace MelonLoader -{ - public delegate void LemonAction(); - public delegate void LemonAction(T1 arg1); - public delegate void LemonAction(T1 arg1, T2 arg2); - public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3); - public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); - public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); - public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); - public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); -} \ No newline at end of file +namespace MelonLoader; + +public delegate void LemonAction(); +public delegate void LemonAction(T1 arg1); +public delegate void LemonAction(T1 arg1, T2 arg2); +public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3); +public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4); +public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); +public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); +public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); +public delegate void LemonAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); \ No newline at end of file diff --git a/MelonLoader/LemonArraySegment.cs b/MelonLoader/LemonArraySegment.cs index 888c65b40..62bfcb598 100644 --- a/MelonLoader/LemonArraySegment.cs +++ b/MelonLoader/LemonArraySegment.cs @@ -2,226 +2,211 @@ using System.Collections; using System.Collections.Generic; -namespace MelonLoader +namespace MelonLoader; + +// Modified Version of System.ArraySegment from .NET Framework's mscorlib.dll +[Serializable] +public class LemonArraySegment : IList, ICollection, IEnumerable, IEnumerable { - // Modified Version of System.ArraySegment from .NET Framework's mscorlib.dll - [Serializable] - public class LemonArraySegment : IList, ICollection, IEnumerable, IEnumerable + /// Gets the original array containing the range of elements that the array segment delimits. + /// The original array that was passed to the constructor, and that contains the range delimited by the . + public T[] Array { get; private set; } + + /// Gets the position of the first element in the range delimited by the array segment, relative to the start of the original array. + /// The position of the first element in the range delimited by the , relative to the start of the original array. + public int Offset { get; private set; } + + /// Gets the number of elements in the range delimited by the array segment. + /// The number of elements in the range delimited by the . + public int Count { get; private set; } + + /// Initializes a new instance of the structure that delimits all the elements in the specified array. + /// The array to wrap. + /// + /// is . + public LemonArraySegment(T[] array) + { + Array = array ?? throw new ArgumentNullException("array"); + Offset = 0; + Count = array.Length; + } + + /// Initializes a new instance of the structure that delimits the specified range of the elements in the specified array. + /// The array containing the range of elements to delimit. + /// The zero-based index of the first element in the range. + /// The number of elements in the range. + /// + /// is . + /// + /// or is less than 0. + /// + /// and do not specify a valid range in . + public LemonArraySegment(T[] array, int offset, int count) + { + if (array == null) + throw new ArgumentNullException("array"); + + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", "Non-negative number required."); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", "Non-negative number required."); + + if (array.Length - offset < count) + throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + + Array = array; + Offset = offset; + Count = count; + } + + /// Returns the hash code for the current instance. + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return Array != null ? Array.GetHashCode() ^ Offset ^ Count : 0; + } + + /// Determines whether the specified object is equal to the current instance. + /// The object to be compared with the current instance. + /// + /// if the specified object is a structure and is equal to the current instance; otherwise, . + public override bool Equals(object obj) + => obj is LemonArraySegment && Equals((LemonArraySegment)obj); + + /// Determines whether the specified structure is equal to the current instance. + /// The structure to compare with the current instance. + /// + /// if the specified structure is equal to the current instance; otherwise, . + public bool Equals(LemonArraySegment obj) + => obj.Array == Array && obj.Offset == Offset && obj.Count == Count; + + /// Indicates whether two structures are equal. + /// The structure on the left side of the equality operator. + /// The structure on the right side of the equality operator. + /// + /// if is equal to ; otherwise, . + public static bool operator ==(LemonArraySegment a, LemonArraySegment b) + => a.Equals(b); + + /// Indicates whether two structures are unequal. + /// The structure on the left side of the inequality operator. + /// The structure on the right side of the inequality operator. + /// + /// if is not equal to ; otherwise, . + public static bool operator !=(LemonArraySegment a, LemonArraySegment b) + => !(a == b); + + T IList.this[int index] { - /// Gets the original array containing the range of elements that the array segment delimits. - /// The original array that was passed to the constructor, and that contains the range delimited by the . - public T[] Array { get; private set; } - - /// Gets the position of the first element in the range delimited by the array segment, relative to the start of the original array. - /// The position of the first element in the range delimited by the , relative to the start of the original array. - public int Offset { get; private set; } - - /// Gets the number of elements in the range delimited by the array segment. - /// The number of elements in the range delimited by the . - public int Count { get; private set; } - - /// Initializes a new instance of the structure that delimits all the elements in the specified array. - /// The array to wrap. - /// - /// is . - public LemonArraySegment(T[] array) + get { - if (array == null) - throw new ArgumentNullException("array"); - Array = array; - Offset = 0; - Count = array.Length; + if (Array == null) + throw new InvalidOperationException("The underlying array is null."); + return index < 0 || index >= Count ? throw new ArgumentOutOfRangeException("index") : Array[Offset + index]; } - - /// Initializes a new instance of the structure that delimits the specified range of the elements in the specified array. - /// The array containing the range of elements to delimit. - /// The zero-based index of the first element in the range. - /// The number of elements in the range. - /// - /// is . - /// - /// or is less than 0. - /// - /// and do not specify a valid range in . - public LemonArraySegment(T[] array, int offset, int count) + set { - if (array == null) - throw new ArgumentNullException("array"); + if (Array == null) + throw new InvalidOperationException("The underlying array is null."); + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException("index"); + Array[Offset + index] = value; + } + } + + int IList.IndexOf(T item) + { + if (Array == null) + throw new InvalidOperationException("The underlying array is null."); + var num = System.Array.IndexOf(Array, item, Offset, Count); + return num < 0 ? -1 : num - Offset; + } + + void IList.Insert(int index, T item) + => throw new NotSupportedException(); - if (offset < 0) - throw new ArgumentOutOfRangeException("offset", "Non-negative number required."); + void IList.RemoveAt(int index) + => throw new NotSupportedException(); - if (count < 0) - throw new ArgumentOutOfRangeException("count", "Non-negative number required."); + bool ICollection.IsReadOnly { get => true; } - if (array.Length - offset < count) - throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + void ICollection.Add(T item) + => throw new NotSupportedException(); - Array = array; - Offset = offset; - Count = count; + void ICollection.Clear() + => throw new NotSupportedException(); + + bool ICollection.Contains(T item) + { + return Array == null + ? throw new InvalidOperationException("The underlying array is null.") + : System.Array.IndexOf(Array, item, Offset, Count) >= 0; + } + + void ICollection.CopyTo(T[] array, int arrayIndex) + { + if (Array == null) + throw new InvalidOperationException("The underlying array is null."); + System.Array.Copy(Array, Offset, array, arrayIndex, Count); + } + + bool ICollection.Remove(T item) + => throw new NotSupportedException(); + + IEnumerator IEnumerable.GetEnumerator() + { + return Array == null ? throw new InvalidOperationException("The underlying array is null.") : (IEnumerator)new LemonArraySegmentEnumerator(this); + } + + /// Returns an enumerator that iterates through an array segment. + /// An enumerator that can be used to iterate through the array segment. + IEnumerator IEnumerable.GetEnumerator() + { + return Array == null ? throw new InvalidOperationException("The underlying array is null.") : (IEnumerator)new LemonArraySegmentEnumerator(this); + } + + [Serializable] + private sealed class LemonArraySegmentEnumerator : IEnumerator, IDisposable, IEnumerator + { + private readonly T[] _array; + private readonly int _start; + private readonly int _end; + private int _current; + + internal LemonArraySegmentEnumerator(LemonArraySegment arraySegment) + { + _array = arraySegment.Array; + _start = arraySegment.Offset; + _end = _start + arraySegment.Count; + _current = _start - 1; } - /// Returns the hash code for the current instance. - /// A 32-bit signed integer hash code. - public override int GetHashCode() - { - if (Array != null) - return Array.GetHashCode() ^ Offset ^ Count; - return 0; - } - - /// Determines whether the specified object is equal to the current instance. - /// The object to be compared with the current instance. - /// - /// if the specified object is a structure and is equal to the current instance; otherwise, . - public override bool Equals(object obj) - => obj is LemonArraySegment && Equals((LemonArraySegment)obj); - - /// Determines whether the specified structure is equal to the current instance. - /// The structure to compare with the current instance. - /// - /// if the specified structure is equal to the current instance; otherwise, . - public bool Equals(LemonArraySegment obj) - => obj.Array == Array && obj.Offset == Offset && obj.Count == Count; - - /// Indicates whether two structures are equal. - /// The structure on the left side of the equality operator. - /// The structure on the right side of the equality operator. - /// - /// if is equal to ; otherwise, . - public static bool operator ==(LemonArraySegment a, LemonArraySegment b) - => a.Equals(b); - - /// Indicates whether two structures are unequal. - /// The structure on the left side of the inequality operator. - /// The structure on the right side of the inequality operator. - /// - /// if is not equal to ; otherwise, . - public static bool operator !=(LemonArraySegment a, LemonArraySegment b) - => !(a == b); - - T IList.this[int index] - { - get - { - if (Array == null) - throw new InvalidOperationException("The underlying array is null."); - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException("index"); - return Array[Offset + index]; - } - set - { - if (Array == null) - throw new InvalidOperationException("The underlying array is null."); - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException("index"); - Array[Offset + index] = value; - } - } - - int IList.IndexOf(T item) - { - if (Array == null) - throw new InvalidOperationException("The underlying array is null."); - int num = System.Array.IndexOf(Array, item, Offset, Count); - if (num < 0) - return -1; - return num - Offset; - } - - void IList.Insert(int index, T item) - => throw new NotSupportedException(); - - void IList.RemoveAt(int index) - => throw new NotSupportedException(); - - bool ICollection.IsReadOnly { get => true; } - - void ICollection.Add(T item) - => throw new NotSupportedException(); - - void ICollection.Clear() - => throw new NotSupportedException(); - - bool ICollection.Contains(T item) - { - if (Array == null) - throw new InvalidOperationException("The underlying array is null."); - return System.Array.IndexOf(Array, item, Offset, Count) >= 0; - } - - void ICollection.CopyTo(T[] array, int arrayIndex) - { - if (Array == null) - throw new InvalidOperationException("The underlying array is null."); - System.Array.Copy(Array, Offset, array, arrayIndex, Count); - } - - bool ICollection.Remove(T item) - => throw new NotSupportedException(); - - IEnumerator IEnumerable.GetEnumerator() - { - if (Array == null) - throw new InvalidOperationException("The underlying array is null."); - return new LemonArraySegmentEnumerator(this); - } - - /// Returns an enumerator that iterates through an array segment. - /// An enumerator that can be used to iterate through the array segment. - IEnumerator IEnumerable.GetEnumerator() - { - if (Array == null) - throw new InvalidOperationException("The underlying array is null."); - return new LemonArraySegmentEnumerator(this); - } - - [Serializable] - private sealed class LemonArraySegmentEnumerator : IEnumerator, IDisposable, IEnumerator - { - private T[] _array; - private int _start; - private int _end; - private int _current; - - internal LemonArraySegmentEnumerator(LemonArraySegment arraySegment) - { - _array = arraySegment.Array; - _start = arraySegment.Offset; - _end = _start + arraySegment.Count; - _current = _start - 1; - } - - public bool MoveNext() - { - if (_current < _end) - { - _current++; - return _current < _end; - } - return false; - } - - public T Current - { - get - { - if (_current < _start) - throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); - if (_current >= _end) - throw new InvalidOperationException("Enumeration already finished."); - return _array[_current]; - } - } - - object IEnumerator.Current { get => Current; } - - void IEnumerator.Reset() - => _current = _start - 1; - - public void Dispose() { } - } - } + public bool MoveNext() + { + if (_current < _end) + { + _current++; + return _current < _end; + } + return false; + } + + public T Current + { + get + { + if (_current < _start) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return _current >= _end ? throw new InvalidOperationException("Enumeration already finished.") : _array[_current]; + } + } + + object IEnumerator.Current { get => Current; } + + void IEnumerator.Reset() + => _current = _start - 1; + + public void Dispose() { } + } } diff --git a/MelonLoader/LemonEnumerator.cs b/MelonLoader/LemonEnumerator.cs index 0a781bc7c..257032865 100644 --- a/MelonLoader/LemonEnumerator.cs +++ b/MelonLoader/LemonEnumerator.cs @@ -1,73 +1,71 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; -namespace MelonLoader +namespace MelonLoader; + +public class LemonEnumerator : IEnumerator, IEnumerable { - public class LemonEnumerator : IEnumerator, IEnumerable - { - private T[] LemonPatch; - private int NextLemon = 0; + private T[] LemonPatch; + private int NextLemon = 0; - /// - /// Creates a new instance of with a new copy of ''. - /// - public LemonEnumerator(T[] lemons) - => LemonPatch = lemons.ToArray(); + /// + /// Creates a new instance of with a new copy of ''. + /// + public LemonEnumerator(T[] lemons) + => LemonPatch = lemons.ToArray(); - /// - /// Creates a new instance of with a new copy of ''. - /// - public LemonEnumerator(IList lemons) - => LemonPatch = lemons.ToArray(); + /// + /// Creates a new instance of with a new copy of ''. + /// + public LemonEnumerator(IList lemons) + => LemonPatch = lemons.ToArray(); - object IEnumerator.Current => Current; - public T Current { get; private set; } + object IEnumerator.Current => Current; + public T Current { get; private set; } - public bool Peek(out T next) + public bool Peek(out T next) + { + if ((LemonPatch == null) + || (LemonPatch.Length <= 0) + || (NextLemon >= LemonPatch.Length)) { - if ((LemonPatch == null) - || (LemonPatch.Length <= 0) - || (NextLemon >= LemonPatch.Length)) - { - next = Current; - return false; - } - - next = LemonPatch[NextLemon]; - return true; + next = Current; + return false; } - bool IEnumerator.MoveNext() => MoveNext(); - public bool MoveNext() - { - if ((LemonPatch == null) - || (LemonPatch.Length <= 0) - || (NextLemon >= LemonPatch.Length)) - return false; - Current = LemonPatch[NextLemon]; - NextLemon++; - return true; - } + next = LemonPatch[NextLemon]; + return true; + } - void IEnumerator.Reset() => Reset(); - public void Reset() - { - NextLemon = 0; - Current = default; - } + bool IEnumerator.MoveNext() => MoveNext(); + public bool MoveNext() + { + if ((LemonPatch == null) + || (LemonPatch.Length <= 0) + || (NextLemon >= LemonPatch.Length)) + return false; + Current = LemonPatch[NextLemon]; + NextLemon++; + return true; + } + + void IEnumerator.Reset() => Reset(); + public void Reset() + { + NextLemon = 0; + Current = default; + } - public IEnumerator GetEnumerator() // for foreach loops - => this; + public IEnumerator GetEnumerator() // for foreach loops + => this; - IEnumerator IEnumerable.GetEnumerator() - => this; + IEnumerator IEnumerable.GetEnumerator() + => this; - public void Dispose() - { - Reset(); - LemonPatch = null; - } + public void Dispose() + { + Reset(); + LemonPatch = null; } } \ No newline at end of file diff --git a/MelonLoader/LemonFunc.cs b/MelonLoader/LemonFunc.cs index a24e71a9f..442cffe39 100644 --- a/MelonLoader/LemonFunc.cs +++ b/MelonLoader/LemonFunc.cs @@ -1,12 +1,11 @@ -namespace MelonLoader -{ - public delegate RT LemonFunc(); - public delegate RT LemonFunc(T1 arg1); - public delegate RT LemonFunc(T1 arg1, T2 arg2); - public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3); - public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); - public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); - public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); - public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); -} \ No newline at end of file +namespace MelonLoader; + +public delegate RT LemonFunc(); +public delegate RT LemonFunc(T1 arg1); +public delegate RT LemonFunc(T1 arg1, T2 arg2); +public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3); +public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); +public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); +public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); +public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); +public delegate RT LemonFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); \ No newline at end of file diff --git a/MelonLoader/LemonTuple.cs b/MelonLoader/LemonTuple.cs index e2929084c..bbb776d26 100644 --- a/MelonLoader/LemonTuple.cs +++ b/MelonLoader/LemonTuple.cs @@ -1,113 +1,112 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[Serializable] +public class LemonTuple + : object { - [Serializable] - public class LemonTuple - : object - { - public T1 Item1; - public LemonTuple() { } - public LemonTuple(T1 item1) - { Item1 = item1; } - } + public T1 Item1; + public LemonTuple() { } + public LemonTuple(T1 item1) + { Item1 = item1; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T2 Item2; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2) - : base(item1) - { Item2 = item2; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T2 Item2; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2) + : base(item1) + { Item2 = item2; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T3 Item3; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3) - : base(item1, item2) - { Item3 = item3; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T3 Item3; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3) + : base(item1, item2) + { Item3 = item3; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T4 Item4; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4) - : base(item1, item2, item3) - { Item4 = item4; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T4 Item4; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4) + : base(item1, item2, item3) + { Item4 = item4; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T5 Item5; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) - : base(item1, item2, item3, item4) - { Item5 = item5; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T5 Item5; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) + : base(item1, item2, item3, item4) + { Item5 = item5; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T6 Item6; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) - : base(item1, item2, item3, item4, item5) - { Item6 = item6; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T6 Item6; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) + : base(item1, item2, item3, item4, item5) + { Item6 = item6; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T7 Item7; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) - : base(item1, item2, item3, item4, item5, item6) - { Item7 = item7; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T7 Item7; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) + : base(item1, item2, item3, item4, item5, item6) + { Item7 = item7; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T8 Item8; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) - : base(item1, item2, item3, item4, item5, item6, item7) - { Item8 = item8; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T8 Item8; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) + : base(item1, item2, item3, item4, item5, item6, item7) + { Item8 = item8; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T9 Item9; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9) - : base(item1, item2, item3, item4, item5, item6, item7, item8) - { Item9 = item9; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T9 Item9; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9) + : base(item1, item2, item3, item4, item5, item6, item7, item8) + { Item9 = item9; } +} - [Serializable] - public class LemonTuple - : LemonTuple - { - public T10 Item10; - public LemonTuple() : base() { } - public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9, T10 item10) - : base(item1, item2, item3, item4, item5, item6, item7, item8, item9) - { Item10 = item10; } - } +[Serializable] +public class LemonTuple + : LemonTuple +{ + public T10 Item10; + public LemonTuple() : base() { } + public LemonTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9, T10 item10) + : base(item1, item2, item3, item4, item5, item6, item7, item8, item9) + { Item10 = item10; } } diff --git a/MelonLoader/Lemons/Cryptography/LemonMD5.cs b/MelonLoader/Lemons/Cryptography/LemonMD5.cs index 475b655cd..77cf77807 100644 --- a/MelonLoader/Lemons/Cryptography/LemonMD5.cs +++ b/MelonLoader/Lemons/Cryptography/LemonMD5.cs @@ -1,25 +1,24 @@ using System.IO; using System.Security.Cryptography; -namespace MelonLoader.Lemons.Cryptography +namespace MelonLoader.Lemons.Cryptography; + +public class LemonMD5 { - public class LemonMD5 - { - private HashAlgorithm algorithm; - private static LemonMD5 static_algorithm = new(); + private readonly HashAlgorithm algorithm; + private static readonly LemonMD5 static_algorithm = new(); - public LemonMD5() - { - algorithm = (HashAlgorithm)CryptoConfig.CreateFromName("System.Security.Cryptography.MD5"); - algorithm.SetHashSizeValue(256); - } + public LemonMD5() + { + algorithm = (HashAlgorithm)CryptoConfig.CreateFromName("System.Security.Cryptography.MD5"); + algorithm.SetHashSizeValue(256); + } - public static byte[] ComputeMD5Hash(byte[] buffer) => static_algorithm.ComputeHash(buffer); - public static byte[] ComputeMD5Hash(byte[] buffer, int offset, int count) => static_algorithm.ComputeHash(buffer, offset, count); - public static byte[] ComputeMD5Hash(Stream inputStream) => static_algorithm.ComputeHash(inputStream); + public static byte[] ComputeMD5Hash(byte[] buffer) => static_algorithm.ComputeHash(buffer); + public static byte[] ComputeMD5Hash(byte[] buffer, int offset, int count) => static_algorithm.ComputeHash(buffer, offset, count); + public static byte[] ComputeMD5Hash(Stream inputStream) => static_algorithm.ComputeHash(inputStream); - public byte[] ComputeHash(byte[] buffer) => algorithm.ComputeHash(buffer); - public byte[] ComputeHash(byte[] buffer, int offset, int count) => algorithm.ComputeHash(buffer, offset, count); - public byte[] ComputeHash(Stream inputStream) => algorithm.ComputeHash(inputStream); - } + public byte[] ComputeHash(byte[] buffer) => algorithm.ComputeHash(buffer); + public byte[] ComputeHash(byte[] buffer, int offset, int count) => algorithm.ComputeHash(buffer, offset, count); + public byte[] ComputeHash(Stream inputStream) => algorithm.ComputeHash(inputStream); } diff --git a/MelonLoader/Lemons/Cryptography/LemonSHA256.cs b/MelonLoader/Lemons/Cryptography/LemonSHA256.cs index be2ab6115..355abe2a2 100644 --- a/MelonLoader/Lemons/Cryptography/LemonSHA256.cs +++ b/MelonLoader/Lemons/Cryptography/LemonSHA256.cs @@ -1,26 +1,24 @@ -using System; -using System.IO; +using System.IO; using System.Security.Cryptography; -namespace MelonLoader.Lemons.Cryptography +namespace MelonLoader.Lemons.Cryptography; + +public class LemonSHA256 { - public class LemonSHA256 - { - private HashAlgorithm algorithm; - private static LemonSHA256 static_algorithm = new(); + private readonly HashAlgorithm algorithm; + private static readonly LemonSHA256 static_algorithm = new(); - public LemonSHA256() - { - algorithm = (HashAlgorithm)CryptoConfig.CreateFromName("System.Security.Cryptography.SHA256"); - algorithm.SetHashSizeValue(256); - } + public LemonSHA256() + { + algorithm = (HashAlgorithm)CryptoConfig.CreateFromName("System.Security.Cryptography.SHA256"); + algorithm.SetHashSizeValue(256); + } - public static byte[] ComputeSHA256Hash(byte[] buffer) => static_algorithm.ComputeHash(buffer); - public static byte[] ComputeSHA256Hash(byte[] buffer, int offset, int count) => static_algorithm.ComputeHash(buffer, offset, count); - public static byte[] ComputeSHA256Hash(Stream inputStream) => static_algorithm.ComputeHash(inputStream); + public static byte[] ComputeSHA256Hash(byte[] buffer) => static_algorithm.ComputeHash(buffer); + public static byte[] ComputeSHA256Hash(byte[] buffer, int offset, int count) => static_algorithm.ComputeHash(buffer, offset, count); + public static byte[] ComputeSHA256Hash(Stream inputStream) => static_algorithm.ComputeHash(inputStream); - public byte[] ComputeHash(byte[] buffer) => algorithm.ComputeHash(buffer); - public byte[] ComputeHash(byte[] buffer, int offset, int count) => algorithm.ComputeHash(buffer, offset, count); - public byte[] ComputeHash(Stream inputStream) => algorithm.ComputeHash(inputStream); - } + public byte[] ComputeHash(byte[] buffer) => algorithm.ComputeHash(buffer); + public byte[] ComputeHash(byte[] buffer, int offset, int count) => algorithm.ComputeHash(buffer, offset, count); + public byte[] ComputeHash(Stream inputStream) => algorithm.ComputeHash(inputStream); } diff --git a/MelonLoader/Lemons/Cryptography/LemonSHA512.cs b/MelonLoader/Lemons/Cryptography/LemonSHA512.cs index fac019107..7c985e1c5 100644 --- a/MelonLoader/Lemons/Cryptography/LemonSHA512.cs +++ b/MelonLoader/Lemons/Cryptography/LemonSHA512.cs @@ -1,25 +1,24 @@ using System.IO; using System.Security.Cryptography; -namespace MelonLoader.Lemons.Cryptography +namespace MelonLoader.Lemons.Cryptography; + +public class LemonSHA512 { - public class LemonSHA512 - { - private HashAlgorithm algorithm; - private static LemonSHA512 static_algorithm = new(); + private readonly HashAlgorithm algorithm; + private static readonly LemonSHA512 static_algorithm = new(); - public LemonSHA512() - { - algorithm = (HashAlgorithm)CryptoConfig.CreateFromName("System.Security.Cryptography.SHA512"); - algorithm.SetHashSizeValue(512); - } + public LemonSHA512() + { + algorithm = (HashAlgorithm)CryptoConfig.CreateFromName("System.Security.Cryptography.SHA512"); + algorithm.SetHashSizeValue(512); + } - public static byte[] ComputeSHA512Hash(byte[] buffer) => static_algorithm.ComputeHash(buffer); - public static byte[] ComputeSHA512Hash(byte[] buffer, int offset, int count) => static_algorithm.ComputeHash(buffer, offset, count); - public static byte[] ComputeSHA512Hash(Stream inputStream) => static_algorithm.ComputeHash(inputStream); + public static byte[] ComputeSHA512Hash(byte[] buffer) => static_algorithm.ComputeHash(buffer); + public static byte[] ComputeSHA512Hash(byte[] buffer, int offset, int count) => static_algorithm.ComputeHash(buffer, offset, count); + public static byte[] ComputeSHA512Hash(Stream inputStream) => static_algorithm.ComputeHash(inputStream); - public byte[] ComputeHash(byte[] buffer) => algorithm.ComputeHash(buffer); - public byte[] ComputeHash(byte[] buffer, int offset, int count) => algorithm.ComputeHash(buffer, offset, count); - public byte[] ComputeHash(Stream inputStream) => algorithm.ComputeHash(inputStream); - } + public byte[] ComputeHash(byte[] buffer) => algorithm.ComputeHash(buffer); + public byte[] ComputeHash(byte[] buffer, int offset, int count) => algorithm.ComputeHash(buffer, offset, count); + public byte[] ComputeHash(Stream inputStream) => algorithm.ComputeHash(inputStream); } diff --git a/MelonLoader/Melon.cs b/MelonLoader/Melon.cs index 682aa7429..c257ba73b 100644 --- a/MelonLoader/Melon.cs +++ b/MelonLoader/Melon.cs @@ -1,25 +1,24 @@ -namespace MelonLoader +namespace MelonLoader; + +public static class Melon where T : MelonBase { - public static class Melon where T : MelonBase - { - private static T _instance; + private static T _instance; - public static T Instance + public static T Instance + { + get { - get - { - if (_instance != null) - return _instance; + if (_instance != null) + return _instance; - var melon = MelonAssembly.FindMelonInstance(); - if (melon == null) - return null; + var melon = MelonAssembly.FindMelonInstance(); + if (melon == null) + return null; - _instance = melon; - return melon; - } + _instance = melon; + return melon; } - - public static MelonLogger.Instance Logger => Instance?.LoggerInstance; } + + public static MelonLogger.Instance Logger => Instance?.LoggerInstance; } diff --git a/MelonLoader/MelonAction.cs b/MelonLoader/MelonAction.cs index bf65d1376..9cd49dcc0 100644 --- a/MelonLoader/MelonAction.cs +++ b/MelonLoader/MelonAction.cs @@ -1,36 +1,35 @@ using System; using System.Collections.Generic; -namespace MelonLoader +namespace MelonLoader; + +internal class MelonAction where T : Delegate { - internal class MelonAction where T : Delegate - { - internal readonly T del; - internal readonly bool unsubscribeOnFirstInvocation; - internal readonly int priority; + internal readonly T del; + internal readonly bool unsubscribeOnFirstInvocation; + internal readonly int priority; - internal readonly MelonAssembly melonAssembly; + internal readonly MelonAssembly melonAssembly; - private MelonAction(T singleDel, int priority, bool unsubscribeOnFirstInvocation) - { - del = singleDel; - melonAssembly = MelonAssembly.GetMelonAssemblyOfMember(del.Method, del.Target); - this.priority = priority; - this.unsubscribeOnFirstInvocation = unsubscribeOnFirstInvocation; - } + private MelonAction(T singleDel, int priority, bool unsubscribeOnFirstInvocation) + { + del = singleDel; + melonAssembly = MelonAssembly.GetMelonAssemblyOfMember(del.Method, del.Target); + this.priority = priority; + this.unsubscribeOnFirstInvocation = unsubscribeOnFirstInvocation; + } - internal static List> Get(T del, int priority = 0, bool unsubscribeOnFirstInvocation = false) + internal static List> Get(T del, int priority = 0, bool unsubscribeOnFirstInvocation = false) + { + var mets = del.GetInvocationList(); + var result = new List>(); + foreach (var met in mets) { - var mets = del.GetInvocationList(); - var result = new List>(); - foreach (var met in mets) - { - if (met.Target != null && met.Target is MelonBase melon && !melon.Registered) - continue; + if (met.Target != null && met.Target is MelonBase melon && !melon.Registered) + continue; - result.Add(new MelonAction((T)met, priority, unsubscribeOnFirstInvocation)); - } - return result; + result.Add(new MelonAction((T)met, priority, unsubscribeOnFirstInvocation)); } + return result; } } diff --git a/MelonLoader/MelonAdditionalCreditsAttribute.cs b/MelonLoader/MelonAdditionalCreditsAttribute.cs index 5af34752e..85f4cc818 100644 --- a/MelonLoader/MelonAdditionalCreditsAttribute.cs +++ b/MelonLoader/MelonAdditionalCreditsAttribute.cs @@ -1,19 +1,20 @@ using System; -namespace MelonLoader { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonAdditionalCreditsAttribute : Attribute { - /// - /// Any additional credits that the mod author might want to include - /// - public string Credits { get; internal set; } +namespace MelonLoader; +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonAdditionalCreditsAttribute : Attribute +{ + /// + /// Any additional credits that the mod author might want to include + /// + public string Credits { get; internal set; } - /// - /// AdditionalCredits constructor - /// - /// The additional credits of the mod - public MelonAdditionalCreditsAttribute(string credits) { - Credits = credits; - } + /// + /// AdditionalCredits constructor + /// + /// The additional credits of the mod + public MelonAdditionalCreditsAttribute(string credits) + { + Credits = credits; } } diff --git a/MelonLoader/MelonAdditionalDependenciesAttribute.cs b/MelonLoader/MelonAdditionalDependenciesAttribute.cs index b8c91d120..7a7968288 100644 --- a/MelonLoader/MelonAdditionalDependenciesAttribute.cs +++ b/MelonLoader/MelonAdditionalDependenciesAttribute.cs @@ -1,15 +1,14 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonAdditionalDependenciesAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonAdditionalDependenciesAttribute : Attribute - { - /// - /// The (simple) assembly names of Additional Dependencies that aren't directly referenced but should still be regarded as important. - /// - public string[] AssemblyNames { get; internal set; } + /// + /// The (simple) assembly names of Additional Dependencies that aren't directly referenced but should still be regarded as important. + /// + public string[] AssemblyNames { get; internal set; } - public MelonAdditionalDependenciesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } - } + public MelonAdditionalDependenciesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } } \ No newline at end of file diff --git a/MelonLoader/MelonAssembly.cs b/MelonLoader/MelonAssembly.cs index 8806bdc03..6b3868f18 100644 --- a/MelonLoader/MelonAssembly.cs +++ b/MelonLoader/MelonAssembly.cs @@ -1,312 +1,310 @@ -using Semver; +using MelonLoader.Utils; +using Semver; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.IO; using System.Reflection; -using MelonLoader.Utils; #if NET6_0_OR_GREATER using System.Runtime.Loader; #endif -namespace MelonLoader +namespace MelonLoader; + +public sealed class MelonAssembly { - public sealed class MelonAssembly - { - #region Static + #region Static - /// - /// Called before a process of resolving Melons from a MelonAssembly has started. - /// - public static readonly MelonEvent OnAssemblyResolving = new(); + /// + /// Called before a process of resolving Melons from a MelonAssembly has started. + /// + public static readonly MelonEvent OnAssemblyResolving = new(); - public static event LemonFunc CustomMelonResolvers; + public static event LemonFunc CustomMelonResolvers; - internal static List loadedAssemblies = new(); + internal static List loadedAssemblies = []; - /// - /// List of all loaded MelonAssemblies. - /// - public static ReadOnlyCollection LoadedAssemblies => loadedAssemblies.AsReadOnly(); + /// + /// List of all loaded MelonAssemblies. + /// + public static ReadOnlyCollection LoadedAssemblies => loadedAssemblies.AsReadOnly(); - /// - /// Tries to find the instance of Melon with type T, whether it's registered or not - /// - public static T FindMelonInstance() where T : MelonBase + /// + /// Tries to find the instance of Melon with type T, whether it's registered or not + /// + public static T FindMelonInstance() where T : MelonBase + { + foreach (var asm in loadedAssemblies) { - foreach (var asm in loadedAssemblies) + foreach (var melon in asm.loadedMelons) { - foreach (var melon in asm.loadedMelons) - { - if (melon is T teaMelon) - return teaMelon; - } + if (melon is T teaMelon) + return teaMelon; } - - return null; } - /// - /// Gets the MelonAssembly of the given member. If the given member is not in any MelonAssembly, returns null. - /// - public static MelonAssembly GetMelonAssemblyOfMember(MemberInfo member, object obj = null) - { - if (member == null) - return null; + return null; + } - if (obj != null && obj is MelonBase melon) - return melon.MelonAssembly; + /// + /// Gets the MelonAssembly of the given member. If the given member is not in any MelonAssembly, returns null. + /// + public static MelonAssembly GetMelonAssemblyOfMember(MemberInfo member, object obj = null) + { + if (member == null) + return null; - var name = member.DeclaringType.Assembly.FullName; - var ma = loadedAssemblies.Find(x => x.Assembly.FullName == name); - return ma; - } + if (obj is not null and MelonBase melon) + return melon.MelonAssembly; - /// - /// Loads or finds a MelonAssembly from path. - /// - /// Path of the MelonAssembly - /// Sets whether Melons should be auto-loaded or not - public static MelonAssembly LoadMelonAssembly(string path, bool loadMelons = true) + var name = member.DeclaringType.Assembly.FullName; + var ma = loadedAssemblies.Find(x => x.Assembly.FullName == name); + return ma; + } + + /// + /// Loads or finds a MelonAssembly from path. + /// + /// Path of the MelonAssembly + /// Sets whether Melons should be auto-loaded or not + public static MelonAssembly LoadMelonAssembly(string path, bool loadMelons = true) + { + if (path == null) { - if (path == null) - { - MelonLogger.Error("Failed to load a Melon Assembly: Path cannot be null."); - return null; - } + MelonLogger.Error("Failed to load a Melon Assembly: Path cannot be null."); + return null; + } - path = Path.GetFullPath(path); + path = Path.GetFullPath(path); - try - { + try + { #if NET6_0_OR_GREATER - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path); + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path); #else - var assembly = Assembly.LoadFrom(path); + var assembly = Assembly.LoadFrom(path); #endif - return LoadMelonAssembly(path, assembly, loadMelons); - } - catch (Exception ex) - { - MelonLogger.Error($"Failed to load Melon Assembly from '{path}':\n{ex}"); - return null; - } + return LoadMelonAssembly(path, assembly, loadMelons); } + catch (Exception ex) + { + MelonLogger.Error($"Failed to load Melon Assembly from '{path}':\n{ex}"); + return null; + } + } - /// - /// Loads or finds a MelonAssembly from raw Assembly Data. - /// - public static MelonAssembly LoadRawMelonAssembly(string path, byte[] assemblyData, byte[] symbolsData = null, bool loadMelons = true) + /// + /// Loads or finds a MelonAssembly from raw Assembly Data. + /// + public static MelonAssembly LoadRawMelonAssembly(string path, byte[] assemblyData, byte[] symbolsData = null, bool loadMelons = true) + { + if (assemblyData == null) { - if (assemblyData == null) - { - MelonLogger.Error("Failed to load a Melon Assembly: assemblyData cannot be null."); - return null; - } + MelonLogger.Error("Failed to load a Melon Assembly: assemblyData cannot be null."); + return null; + } - try - { + try + { #if NET6_0_OR_GREATER - var fileStream = new MemoryStream(assemblyData); - var symStream = symbolsData == null ? null : new MemoryStream(symbolsData); + var fileStream = new MemoryStream(assemblyData); + var symStream = symbolsData == null ? null : new MemoryStream(symbolsData); - var assembly = AssemblyLoadContext.Default.LoadFromStream(fileStream, symStream); + var assembly = AssemblyLoadContext.Default.LoadFromStream(fileStream, symStream); #else - var assembly = symbolsData != null ? Assembly.Load(assemblyData, symbolsData) : Assembly.Load(assemblyData); + var assembly = symbolsData != null ? Assembly.Load(assemblyData, symbolsData) : Assembly.Load(assemblyData); #endif - return LoadMelonAssembly(path, assembly, loadMelons); - } - catch (Exception ex) - { - MelonLogger.Error($"Failed to load Melon Assembly from raw Assembly Data (length {assemblyData.Length}):\n{ex}"); - return null; - } + return LoadMelonAssembly(path, assembly, loadMelons); } - - /// - /// Loads or finds a MelonAssembly. - /// - public static MelonAssembly LoadMelonAssembly(string path, Assembly assembly, bool loadMelons = true) + catch (Exception ex) { - if (!File.Exists(path)) - path = assembly.Location; + MelonLogger.Error($"Failed to load Melon Assembly from raw Assembly Data (length {assemblyData.Length}):\n{ex}"); + return null; + } + } - if (assembly == null) - { - MelonLogger.Error("Failed to load a Melon Assembly: Assembly cannot be null."); - return null; - } + /// + /// Loads or finds a MelonAssembly. + /// + public static MelonAssembly LoadMelonAssembly(string path, Assembly assembly, bool loadMelons = true) + { + if (!File.Exists(path)) + path = assembly.Location; - var ma = loadedAssemblies.Find(x => x.Assembly.FullName == assembly.FullName); - if (ma != null) - return ma; + if (assembly == null) + { + MelonLogger.Error("Failed to load a Melon Assembly: Assembly cannot be null."); + return null; + } - var shortPath = path; - if (shortPath.StartsWith(MelonEnvironment.MelonBaseDirectory)) - shortPath = "." + shortPath.Remove(0, MelonEnvironment.MelonBaseDirectory.Length); + var ma = loadedAssemblies.Find(x => x.Assembly.FullName == assembly.FullName); + if (ma != null) + return ma; - OnAssemblyResolving.Invoke(assembly); - ma = new MelonAssembly(assembly, path); - loadedAssemblies.Add(ma); + var shortPath = path; + if (shortPath.StartsWith(MelonEnvironment.MelonBaseDirectory)) + shortPath = "." + shortPath.Remove(0, MelonEnvironment.MelonBaseDirectory.Length); - if (loadMelons) - ma.LoadMelons(); + OnAssemblyResolving.Invoke(assembly); + ma = new MelonAssembly(assembly, path); + loadedAssemblies.Add(ma); - MelonLogger.MsgDirect(Color.DarkGray, $"Melon Assembly loaded: '{shortPath}'"); - MelonLogger.MsgDirect(Color.DarkGray, $"SHA256 Hash: '{ma.Hash}'"); - return ma; - } + if (loadMelons) + ma.LoadMelons(); - #endregion + MelonLogger.MsgDirect(Color.DarkGray, $"Melon Assembly loaded: '{shortPath}'"); + MelonLogger.MsgDirect(Color.DarkGray, $"SHA256 Hash: '{ma.Hash}'"); + return ma; + } - #region Instance + #endregion - private bool melonsLoaded; + #region Instance - private readonly List loadedMelons = new(); - private readonly List rottenMelons = new(); + private bool melonsLoaded; - public readonly MelonEvent OnUnregister = new(); + private readonly List loadedMelons = []; + private readonly List rottenMelons = []; - public bool HarmonyDontPatchAll { get; private set; } = true; + public readonly MelonEvent OnUnregister = new(); - /// - /// A SHA256 Hash of the Assembly. - /// - public string Hash { get; private set; } + public bool HarmonyDontPatchAll { get; private set; } = true; - public Assembly Assembly { get; private set; } + /// + /// A SHA256 Hash of the Assembly. + /// + public string Hash { get; private set; } - public string Location { get; private set; } + public Assembly Assembly { get; private set; } - /// - /// A list of all loaded Melons in the Assembly. - /// - public ReadOnlyCollection LoadedMelons => loadedMelons.AsReadOnly(); + public string Location { get; private set; } - /// - /// A list of all broken Melons in the Assembly. - /// - public ReadOnlyCollection RottenMelons => rottenMelons.AsReadOnly(); + /// + /// A list of all loaded Melons in the Assembly. + /// + public ReadOnlyCollection LoadedMelons => loadedMelons.AsReadOnly(); - private MelonAssembly(Assembly assembly, string location) - { - Assembly = assembly; - Location = location ?? ""; - Hash = MelonUtils.ComputeSimpleSHA256Hash(Location); - } + /// + /// A list of all broken Melons in the Assembly. + /// + public ReadOnlyCollection RottenMelons => rottenMelons.AsReadOnly(); - /// - /// Unregisters all Melons in this Assembly. - /// - public void UnregisterMelons(string reason = null, bool silent = false) - { - foreach (var m in loadedMelons) - m.UnregisterInstance(reason, silent); + private MelonAssembly(Assembly assembly, string location) + { + Assembly = assembly; + Location = location ?? ""; + Hash = MelonUtils.ComputeSimpleSHA256Hash(Location); + } - OnUnregister.Invoke(); - } + /// + /// Unregisters all Melons in this Assembly. + /// + public void UnregisterMelons(string reason = null, bool silent = false) + { + foreach (var m in loadedMelons) + m.UnregisterInstance(reason, silent); - private void OnApplicationQuit() - { - UnregisterMelons("MelonLoader is deinitializing.", true); - } + OnUnregister.Invoke(); + } - public void LoadMelons() - { - if (melonsLoaded) - return; + private void OnApplicationQuit() + { + UnregisterMelons("MelonLoader is deinitializing.", true); + } - melonsLoaded = true; + public void LoadMelons() + { + if (melonsLoaded) + return; - MelonEvents.OnApplicationDefiniteQuit.Subscribe(OnApplicationQuit); + melonsLoaded = true; - // \/ Custom Resolver \/ - var resolvers = CustomMelonResolvers?.GetInvocationList(); - if (resolvers != null) - foreach (LemonFunc r in resolvers) - { - var customMelon = r.Invoke(Assembly); + MelonEvents.OnApplicationDefiniteQuit.Subscribe(OnApplicationQuit); - loadedMelons.AddRange(customMelon.loadedMelons); - rottenMelons.AddRange(customMelon.rottenMelons); - } + // \/ Custom Resolver \/ + var resolvers = CustomMelonResolvers?.GetInvocationList(); + if (resolvers != null) + foreach (LemonFunc r in resolvers) + { + var customMelon = r.Invoke(Assembly); + loadedMelons.AddRange(customMelon.loadedMelons); + rottenMelons.AddRange(customMelon.rottenMelons); + } + + // \/ Default resolver \/ + var info = MelonUtils.PullAttributeFromAssembly(Assembly); + if (info != null && info.SystemType != null && info.SystemType.IsSubclassOf(typeof(MelonBase))) + { + MelonBase melon; + try + { + melon = (MelonBase)Activator.CreateInstance(info.SystemType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, null, null); + } + catch (Exception ex) + { + melon = null; + rottenMelons.Add(new RottenMelon(info.SystemType, "Failed to create an instance of the Melon.", ex)); + } - // \/ Default resolver \/ - var info = MelonUtils.PullAttributeFromAssembly(Assembly); - if (info != null && info.SystemType != null && info.SystemType.IsSubclassOf(typeof(MelonBase))) + if (melon != null) { - MelonBase melon; - try - { - melon = (MelonBase)Activator.CreateInstance(info.SystemType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, null, null); - } - catch (Exception ex) - { - melon = null; - rottenMelons.Add(new RottenMelon(info.SystemType, "Failed to create an instance of the Melon.", ex)); - } - - if (melon != null) - { - var priorityAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var colorAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var authorColorAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var additionalCreditsAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var procAttrs = MelonUtils.PullAttributesFromAssembly(Assembly); - var gameAttrs = MelonUtils.PullAttributesFromAssembly(Assembly); - var optionalDependenciesAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var idAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var gameVersionAttrs = MelonUtils.PullAttributesFromAssembly(Assembly); - var platformAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var domainAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var mlVersionAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var mlBuildAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - var harmonyDPAAttr = MelonUtils.PullAttributeFromAssembly(Assembly); - - melon.Info = info; - melon.AdditionalCredits = additionalCreditsAttr; - melon.MelonAssembly = this; - melon.Priority = priorityAttr?.Priority ?? 0; - melon.ConsoleColor = colorAttr?.DrawingColor ?? MelonLogger.DefaultMelonColor; - melon.AuthorConsoleColor = authorColorAttr?.DrawingColor ?? MelonLogger.DefaultTextColor; - melon.SupportedProcesses = procAttrs; - melon.Games = gameAttrs; - melon.SupportedGameVersions = gameVersionAttrs; - melon.SupportedPlatforms = platformAttr; - melon.SupportedDomain = domainAttr; - melon.SupportedMLVersion = mlVersionAttr; - melon.SupportedMLBuild = mlBuildAttr; - melon.OptionalDependencies = optionalDependenciesAttr; - melon.ID = idAttr?.ID; - HarmonyDontPatchAll = harmonyDPAAttr != null; - - loadedMelons.Add(melon); - - if (!SemVersion.TryParse(info.Version, out _)) - MelonLogger.Warning($"==Normal users can ignore this warning==\nMelon '{info.Name}' by '{info.Author}' has version '{info.Version}' which does not use the Semantic Versioning format. Versions using formats other than the Semantic Versioning format will not be supported in the future versions of MelonLoader.\nFor more details, see: https://semver.org"); - } + var priorityAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var colorAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var authorColorAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var additionalCreditsAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var procAttrs = MelonUtils.PullAttributesFromAssembly(Assembly); + var gameAttrs = MelonUtils.PullAttributesFromAssembly(Assembly); + var optionalDependenciesAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var idAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var gameVersionAttrs = MelonUtils.PullAttributesFromAssembly(Assembly); + var platformAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var domainAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var mlVersionAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var mlBuildAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + var harmonyDPAAttr = MelonUtils.PullAttributeFromAssembly(Assembly); + + melon.Info = info; + melon.AdditionalCredits = additionalCreditsAttr; + melon.MelonAssembly = this; + melon.Priority = priorityAttr?.Priority ?? 0; + melon.ConsoleColor = colorAttr?.DrawingColor ?? MelonLogger.DefaultMelonColor; + melon.AuthorConsoleColor = authorColorAttr?.DrawingColor ?? MelonLogger.DefaultTextColor; + melon.SupportedProcesses = procAttrs; + melon.Games = gameAttrs; + melon.SupportedGameVersions = gameVersionAttrs; + melon.SupportedPlatforms = platformAttr; + melon.SupportedDomain = domainAttr; + melon.SupportedMLVersion = mlVersionAttr; + melon.SupportedMLBuild = mlBuildAttr; + melon.OptionalDependencies = optionalDependenciesAttr; + melon.ID = idAttr?.ID; + HarmonyDontPatchAll = harmonyDPAAttr != null; + + loadedMelons.Add(melon); + + if (!SemVersion.TryParse(info.Version, out _)) + MelonLogger.Warning($"==Normal users can ignore this warning==\nMelon '{info.Name}' by '{info.Author}' has version '{info.Version}' which does not use the Semantic Versioning format. Versions using formats other than the Semantic Versioning format will not be supported in the future versions of MelonLoader.\nFor more details, see: https://semver.org"); } + } #if NET6_0_OR_GREATER - RegisterTypeInIl2Cpp.RegisterAssembly(Assembly); - RegisterTypeInIl2CppWithInterfaces.RegisterAssembly(Assembly); + RegisterTypeInIl2Cpp.RegisterAssembly(Assembly); + RegisterTypeInIl2CppWithInterfaces.RegisterAssembly(Assembly); #endif - if (rottenMelons.Count != 0) + if (rottenMelons.Count != 0) + { + MelonLogger.Error($"Failed to load {rottenMelons.Count} {"Melon".MakePlural(rottenMelons.Count)} from {Path.GetFileName(Location)}:"); + foreach (var r in rottenMelons) { - MelonLogger.Error($"Failed to load {rottenMelons.Count} {"Melon".MakePlural(rottenMelons.Count)} from {Path.GetFileName(Location)}:"); - foreach (var r in rottenMelons) - { - MelonLogger.Error($"Failed to load Melon '{r.type.FullName}': {r.errorMessage}"); - if (r.exception != null) - MelonLogger.Error(r.exception); - } + MelonLogger.Error($"Failed to load Melon '{r.type.FullName}': {r.errorMessage}"); + if (r.exception != null) + MelonLogger.Error(r.exception); } } - - #endregion } + + #endregion } \ No newline at end of file diff --git a/MelonLoader/MelonAuthorColorAttribute.cs b/MelonLoader/MelonAuthorColorAttribute.cs index b12864705..2d06f1604 100644 --- a/MelonLoader/MelonAuthorColorAttribute.cs +++ b/MelonLoader/MelonAuthorColorAttribute.cs @@ -1,35 +1,34 @@ -using System; +using MelonLoader.Utils; +using System; using System.Drawing; -using MelonLoader.Utils; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonAuthorColorAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonAuthorColorAttribute : Attribute + /// + /// Color of the Author Log. + /// + [Obsolete("Color is obsolete. Use DrawingColor for full Color support.")] + public ConsoleColor Color { - /// - /// Color of the Author Log. - /// - [Obsolete("Color is obsolete. Use DrawingColor for full Color support.")] - public ConsoleColor Color - { - get => LoggerUtils.DrawingColorToConsoleColor(DrawingColor); - set => DrawingColor = LoggerUtils.ConsoleColorToDrawingColor(value); - } + get => LoggerUtils.DrawingColorToConsoleColor(DrawingColor); + set => DrawingColor = LoggerUtils.ConsoleColorToDrawingColor(value); + } - /// - /// Color of the Author Log. - /// - public Color DrawingColor { get; internal set; } + /// + /// Color of the Author Log. + /// + public Color DrawingColor { get; internal set; } - public MelonAuthorColorAttribute() - => DrawingColor = MelonLogger.DefaultTextColor; + public MelonAuthorColorAttribute() + => DrawingColor = MelonLogger.DefaultTextColor; - [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead.")] - public MelonAuthorColorAttribute(ConsoleColor color) - => Color = ((color == ConsoleColor.Black) ? LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultMelonColor) : color); + [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead.")] + public MelonAuthorColorAttribute(ConsoleColor color) + => Color = (color == ConsoleColor.Black) ? LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultMelonColor) : color; - public MelonAuthorColorAttribute(int alpha, int red, int green, int blue) - => DrawingColor = System.Drawing.Color.FromArgb(alpha, red, green, blue); - } + public MelonAuthorColorAttribute(int alpha, int red, int green, int blue) + => DrawingColor = System.Drawing.Color.FromArgb(alpha, red, green, blue); } \ No newline at end of file diff --git a/MelonLoader/MelonBase.cs b/MelonLoader/MelonBase.cs index 63011906a..0178b90dd 100644 --- a/MelonLoader/MelonBase.cs +++ b/MelonLoader/MelonBase.cs @@ -11,656 +11,674 @@ using System.Reflection; #pragma warning disable 0618 -namespace MelonLoader +namespace MelonLoader; + +public abstract class MelonBase { - public abstract class MelonBase + #region Static + + /// + /// Called once a Melon is fully registered. + /// + public static readonly MelonEvent OnMelonRegistered = new(); + + /// + /// Called when a Melon unregisters. + /// + public static readonly MelonEvent OnMelonUnregistered = new(); + + /// + /// Called before a Melon starts initializing. + /// + public static readonly MelonEvent OnMelonInitializing = new(); + + public static ReadOnlyCollection RegisteredMelons => _registeredMelons.AsReadOnly(); + internal static List _registeredMelons = []; + + /// + /// Creates a new Melon instance for a Wrapper. + /// + public static T CreateWrapper(string name, string author, string version, MelonGameAttribute[] games = null, MelonProcessAttribute[] processes = null, int priority = 0, Color? color = null, Color? authorColor = null, string id = null) where T : MelonBase, new() { - #region Static - - /// - /// Called once a Melon is fully registered. - /// - public static readonly MelonEvent OnMelonRegistered = new(); - - /// - /// Called when a Melon unregisters. - /// - public static readonly MelonEvent OnMelonUnregistered = new(); - - /// - /// Called before a Melon starts initializing. - /// - public static readonly MelonEvent OnMelonInitializing = new(); - - public static ReadOnlyCollection RegisteredMelons => _registeredMelons.AsReadOnly(); - internal static List _registeredMelons = new(); - - /// - /// Creates a new Melon instance for a Wrapper. - /// - public static T CreateWrapper(string name, string author, string version, MelonGameAttribute[] games = null, MelonProcessAttribute[] processes = null, int priority = 0, Color? color = null, Color? authorColor = null, string id = null) where T : MelonBase, new() + var melon = new T { - var melon = new T - { - Info = new MelonInfoAttribute(typeof(T), name, version, author), - MelonAssembly = MelonAssembly.LoadMelonAssembly(null, typeof(T).Assembly), - Priority = priority, - ConsoleColor = color ?? MelonLogger.DefaultMelonColor, - AuthorConsoleColor = authorColor ?? MelonLogger.DefaultTextColor, - SupportedProcesses = processes, - Games = games, - OptionalDependencies = null, - ID = id - }; - - return melon; - } + Info = new MelonInfoAttribute(typeof(T), name, version, author), + MelonAssembly = MelonAssembly.LoadMelonAssembly(null, typeof(T).Assembly), + Priority = priority, + ConsoleColor = color ?? MelonLogger.DefaultMelonColor, + AuthorConsoleColor = authorColor ?? MelonLogger.DefaultTextColor, + SupportedProcesses = processes, + Games = games, + OptionalDependencies = null, + ID = id + }; + + return melon; + } - /// - /// Registers a List of Melons in the right order. - /// - public static void RegisterSorted(IEnumerable melons) where T : MelonBase - { - if (melons == null) - return; + /// + /// Registers a List of Melons in the right order. + /// + public static void RegisterSorted(IEnumerable melons) where T : MelonBase + { + if (melons == null) + return; - var collection = melons.ToList(); - SortMelons(ref collection); + var collection = melons.ToList(); + SortMelons(ref collection); - foreach (var m in collection) - m.Register(); - } + foreach (var m in collection) + m.Register(); + } - private static void SortMelons(ref List melons) where T : MelonBase - { - DependencyGraph.TopologicalSort(melons); - melons = melons.OrderBy(x => x.Priority).ToList(); - } + private static void SortMelons(ref List melons) where T : MelonBase + { + DependencyGraph.TopologicalSort(melons); + melons = melons.OrderBy(x => x.Priority).ToList(); + } - #endregion + #endregion - #region Instance + #region Instance - private MelonGameAttribute[] _games = new MelonGameAttribute[0]; - private MelonProcessAttribute[] _processes = new MelonProcessAttribute[0]; - private MelonGameVersionAttribute[] _gameVersions = new MelonGameVersionAttribute[0]; + private MelonGameAttribute[] _games = new MelonGameAttribute[0]; + private MelonProcessAttribute[] _processes = new MelonProcessAttribute[0]; + private MelonGameVersionAttribute[] _gameVersions = new MelonGameVersionAttribute[0]; - public readonly MelonEvent OnRegister = new(); - public readonly MelonEvent OnUnregister = new(); + public readonly MelonEvent OnRegister = new(); + public readonly MelonEvent OnUnregister = new(); - /// - /// MelonAssembly of the Melon. - /// - public MelonAssembly MelonAssembly { get; internal set; } + /// + /// MelonAssembly of the Melon. + /// + public MelonAssembly MelonAssembly { get; internal set; } - /// - /// Priority of the Melon. - /// - public int Priority { get; internal set; } + /// + /// Priority of the Melon. + /// + public int Priority { get; internal set; } - /// - /// Console Color of the Melon. - /// - public Color ConsoleColor { get; internal set; } + /// + /// Console Color of the Melon. + /// + public Color ConsoleColor { get; internal set; } - /// - /// Console Color of the Author that made this melon. - /// - public Color AuthorConsoleColor { get; internal set; } + /// + /// Console Color of the Author that made this melon. + /// + public Color AuthorConsoleColor { get; internal set; } - /// - /// Info Attribute of the Melon. - /// - public MelonInfoAttribute Info { get; internal set; } + /// + /// Info Attribute of the Melon. + /// + public MelonInfoAttribute Info { get; internal set; } - /// - /// AdditionalCredits Attribute of the Melon - /// - public MelonAdditionalCreditsAttribute AdditionalCredits { get; internal set; } + /// + /// AdditionalCredits Attribute of the Melon + /// + public MelonAdditionalCreditsAttribute AdditionalCredits { get; internal set; } - /// - /// Process Attributes of the Melon. - /// - public MelonProcessAttribute[] SupportedProcesses - { - get => _processes; - internal set => _processes = (value == null || value.Any(x => x.Universal)) ? new MelonProcessAttribute[0] : value; - } + /// + /// Process Attributes of the Melon. + /// + public MelonProcessAttribute[] SupportedProcesses + { + get => _processes; + internal set => _processes = (value == null || value.Any(x => x.Universal)) ? new MelonProcessAttribute[0] : value; + } - /// - /// Game Attributes of the Melon. - /// - public MelonGameAttribute[] Games + /// + /// Game Attributes of the Melon. + /// + public MelonGameAttribute[] Games + { + get => _games; + internal set => _games = (value == null || value.Any(x => x.Universal)) ? new MelonGameAttribute[0] : value; + } + + /// + /// Game Version Attributes of the Melon. + /// + public MelonGameVersionAttribute[] SupportedGameVersions + { + get => _gameVersions; + internal set => _gameVersions = (value == null || value.Any(x => x.Universal)) ? new MelonGameVersionAttribute[0] : value; + } + + /// + /// Optional Dependencies Attribute of the Melon. + /// + public MelonOptionalDependenciesAttribute OptionalDependencies { get; internal set; } + + /// + /// Platform Attribute of the Melon. + /// + public MelonPlatformAttribute SupportedPlatforms { get; internal set; } + + /// + /// Platform Attribute of the Melon. + /// + public MelonPlatformDomainAttribute SupportedDomain { get; internal set; } + + /// + /// Verify Loader Version Attribute of the Melon. + /// + public VerifyLoaderVersionAttribute SupportedMLVersion { get; internal set; } + + /// + /// Verify Build Version Attribute of the Melon. + /// + public VerifyLoaderBuildAttribute SupportedMLBuild { get; internal set; } + + /// + /// Auto-Created Harmony Instance of the Melon. + /// + public HarmonyLib.Harmony HarmonyInstance { get; internal set; } + + /// + /// Auto-Created MelonLogger Instance of the Melon. + /// + public MelonLogger.Instance LoggerInstance { get; internal set; } + + /// + /// Optional ID of the Melon. + /// + public string ID { get; internal set; } + + /// + /// if the Melon is registered. + /// + public bool Registered { get; private set; } + + /// + /// Name of the current Melon Type. + /// + public abstract string MelonTypeName { get; } + + #region Callbacks + + /// + /// Runs before Support Module Initialization and after Assembly Generation for Il2Cpp Games. + /// + public virtual void OnPreSupportModule() { } + + /// + /// Runs once per frame. + /// + public virtual void OnUpdate() { } + + /// + /// Can run multiple times per frame. Mostly used for Physics. + /// + public virtual void OnFixedUpdate() { } + + /// + /// Runs once per frame, after . + /// + public virtual void OnLateUpdate() { } + + /// + /// Can run multiple times per frame. Mostly used for Unity's IMGUI. + /// + public virtual void OnGUI() { } + + /// + /// Runs on a quit request. It is possible to abort the request in this callback. + /// + public virtual void OnApplicationQuit() { } + + /// + /// Runs when Melon Preferences get saved. + /// + public virtual void OnPreferencesSaved() { } + + /// + /// Runs when Melon Preferences get saved. Gets passed the Preferences's File Path. + /// + public virtual void OnPreferencesSaved(string filepath) { } + + /// + /// Runs when Melon Preferences get loaded. + /// + public virtual void OnPreferencesLoaded() { } + + /// + /// Runs when Melon Preferences get loaded. Gets passed the Preferences's File Path. + /// + public virtual void OnPreferencesLoaded(string filepath) { } + + /// + /// Runs when the Melon is registered. Executed before the Melon's info is printed to the console. This callback should only be used a constructor for the Melon. + /// + /// + /// Please note that this callback may run before the Support Module is loaded. + ///
As a result, using unhollowed assemblies may not be possible yet and you would have to override instead.
+ ///
+ public virtual void OnEarlyInitializeMelon() { } + + /// + /// Runs after the Melon has registered. This callback waits until MelonLoader has fully initialized (). + /// + public virtual void OnInitializeMelon() { } + + /// + /// Runs after . This callback waits until Unity has invoked the first 'Start' messages (). + /// + public virtual void OnLateInitializeMelon() { } + + /// + /// Runs when the Melon is unregistered. Also runs before the Application is closed (). + /// + public virtual void OnDeinitializeMelon() { } + + #endregion + + public Incompatibility[] FindIncompatiblities(MelonGameAttribute game, string processName, string gameVersion, + string mlVersion, string mlBuildHashCode, MelonPlatformAttribute.CompatiblePlatforms platform, + MelonPlatformDomainAttribute.CompatibleDomains domain) + => FindIncompatiblities(game, processName, gameVersion, SemVersion.Parse(mlVersion), mlBuildHashCode, platform, domain); + + public Incompatibility[] FindIncompatiblities(MelonGameAttribute game, string processName, string gameVersion, + SemVersion mlVersion, string mlBuildHashCode, MelonPlatformAttribute.CompatiblePlatforms platform, + MelonPlatformDomainAttribute.CompatibleDomains domain) + { + var result = new List(); + if (!(Games.Length == 0 || Games.Any(x => x.IsCompatible(game)))) + result.Add(Incompatibility.Game); + else { - get => _games; - internal set => _games = (value == null || value.Any(x => x.Universal)) ? new MelonGameAttribute[0] : value; - } + if (!(SupportedGameVersions.Length == 0 || SupportedGameVersions.Any(x => x.Version == gameVersion))) + result.Add(Incompatibility.GameVersion); + + if (!(SupportedProcesses.Length == 0 || SupportedProcesses.Any(x => x.IsCompatible(processName)))) + result.Add(Incompatibility.ProcessName); + + if (!(SupportedPlatforms == null || SupportedPlatforms.IsCompatible(platform))) + result.Add(Incompatibility.Platform); - /// - /// Game Version Attributes of the Melon. - /// - public MelonGameVersionAttribute[] SupportedGameVersions + if (!(SupportedDomain == null || SupportedDomain.IsCompatible(domain))) + result.Add(Incompatibility.Domain); + } + if (!(SupportedMLVersion == null || SupportedMLVersion.IsCompatible(mlVersion))) + result.Add(Incompatibility.MLVersion); + else { - get => _gameVersions; - internal set => _gameVersions = (value == null || value.Any(x => x.Universal)) ? new MelonGameVersionAttribute[0] : value; + if (!(SupportedMLBuild == null || SupportedMLBuild.IsCompatible(mlBuildHashCode))) + result.Add(Incompatibility.MLBuild); } - /// - /// Optional Dependencies Attribute of the Melon. - /// - public MelonOptionalDependenciesAttribute OptionalDependencies { get; internal set; } - - /// - /// Platform Attribute of the Melon. - /// - public MelonPlatformAttribute SupportedPlatforms { get; internal set; } - - /// - /// Platform Attribute of the Melon. - /// - public MelonPlatformDomainAttribute SupportedDomain { get; internal set; } - - /// - /// Verify Loader Version Attribute of the Melon. - /// - public VerifyLoaderVersionAttribute SupportedMLVersion { get; internal set; } - - /// - /// Verify Build Version Attribute of the Melon. - /// - public VerifyLoaderBuildAttribute SupportedMLBuild { get; internal set; } - - /// - /// Auto-Created Harmony Instance of the Melon. - /// - public HarmonyLib.Harmony HarmonyInstance { get; internal set; } - - /// - /// Auto-Created MelonLogger Instance of the Melon. - /// - public MelonLogger.Instance LoggerInstance { get; internal set; } - - /// - /// Optional ID of the Melon. - /// - public string ID { get; internal set; } - - /// - /// if the Melon is registered. - /// - public bool Registered { get; private set; } - - /// - /// Name of the current Melon Type. - /// - public abstract string MelonTypeName { get; } - - #region Callbacks - - /// - /// Runs before Support Module Initialization and after Assembly Generation for Il2Cpp Games. - /// - public virtual void OnPreSupportModule() { } - - /// - /// Runs once per frame. - /// - public virtual void OnUpdate() { } - - /// - /// Can run multiple times per frame. Mostly used for Physics. - /// - public virtual void OnFixedUpdate() { } - - /// - /// Runs once per frame, after . - /// - public virtual void OnLateUpdate() { } - - /// - /// Can run multiple times per frame. Mostly used for Unity's IMGUI. - /// - public virtual void OnGUI() { } - - /// - /// Runs on a quit request. It is possible to abort the request in this callback. - /// - public virtual void OnApplicationQuit() { } - - /// - /// Runs when Melon Preferences get saved. - /// - public virtual void OnPreferencesSaved() { } - - /// - /// Runs when Melon Preferences get saved. Gets passed the Preferences's File Path. - /// - public virtual void OnPreferencesSaved(string filepath) { } - - /// - /// Runs when Melon Preferences get loaded. - /// - public virtual void OnPreferencesLoaded() { } - - /// - /// Runs when Melon Preferences get loaded. Gets passed the Preferences's File Path. - /// - public virtual void OnPreferencesLoaded(string filepath) { } - - /// - /// Runs when the Melon is registered. Executed before the Melon's info is printed to the console. This callback should only be used a constructor for the Melon. - /// - /// - /// Please note that this callback may run before the Support Module is loaded. - ///
As a result, using unhollowed assemblies may not be possible yet and you would have to override instead.
- ///
- public virtual void OnEarlyInitializeMelon() { } - - /// - /// Runs after the Melon has registered. This callback waits until MelonLoader has fully initialized (). - /// - public virtual void OnInitializeMelon() { } - - /// - /// Runs after . This callback waits until Unity has invoked the first 'Start' messages (). - /// - public virtual void OnLateInitializeMelon() { } - - /// - /// Runs when the Melon is unregistered. Also runs before the Application is closed (). - /// - public virtual void OnDeinitializeMelon() { } - - #endregion - - public Incompatibility[] FindIncompatiblities(MelonGameAttribute game, string processName, string gameVersion, - string mlVersion, string mlBuildHashCode, MelonPlatformAttribute.CompatiblePlatforms platform, - MelonPlatformDomainAttribute.CompatibleDomains domain) - => FindIncompatiblities(game, processName, gameVersion, SemVersion.Parse(mlVersion), mlBuildHashCode, platform, domain); - - public Incompatibility[] FindIncompatiblities(MelonGameAttribute game, string processName, string gameVersion, - SemVersion mlVersion, string mlBuildHashCode, MelonPlatformAttribute.CompatiblePlatforms platform, - MelonPlatformDomainAttribute.CompatibleDomains domain) - { - var result = new List(); - if (!(Games.Length == 0 || Games.Any(x => x.IsCompatible(game)))) - result.Add(Incompatibility.Game); - else - { - if (!(SupportedGameVersions.Length == 0 || SupportedGameVersions.Any(x => x.Version == gameVersion))) - result.Add(Incompatibility.GameVersion); + return result.ToArray(); + } - if (!(SupportedProcesses.Length == 0 || SupportedProcesses.Any(x => x.IsCompatible(processName)))) - result.Add(Incompatibility.ProcessName); + public Incompatibility[] FindIncompatiblitiesFromContext() + { + return FindIncompatiblities(MelonUtils.CurrentGameAttribute, Process.GetCurrentProcess().ProcessName, MelonUtils.GameVersion, BuildInfo.VersionNumber, MelonUtils.HashCode, MelonUtils.CurrentPlatform, MelonUtils.CurrentDomain); + } - if (!(SupportedPlatforms == null || SupportedPlatforms.IsCompatible(platform))) - result.Add(Incompatibility.Platform); + public static void PrintIncompatibilities(Incompatibility[] incompatibilities, MelonBase melon) + { + if (incompatibilities == null || incompatibilities.Length == 0) + return; - if (!(SupportedDomain == null || SupportedDomain.IsCompatible(domain))) - result.Add(Incompatibility.Domain); - } - if (!(SupportedMLVersion == null || SupportedMLVersion.IsCompatible(mlVersion))) - result.Add(Incompatibility.MLVersion); - else - { - if (!(SupportedMLBuild == null || SupportedMLBuild.IsCompatible(mlBuildHashCode))) - result.Add(Incompatibility.MLBuild); - } + MelonLogger.WriteLine(Color.Red); + MelonLogger.MsgDirect(Color.DarkRed, $"'{melon.Info.Name} v{melon.Info.Version}' is incompatible:"); + if (incompatibilities.Contains(Incompatibility.Game)) + { + MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Games:"); - return result.ToArray(); + foreach (var g in melon.Games) + MelonLogger.MsgDirect($" - '{g.Name}' by {g.Developer}"); } - - public Incompatibility[] FindIncompatiblitiesFromContext() + if (incompatibilities.Contains(Incompatibility.GameVersion)) { - return FindIncompatiblities(MelonUtils.CurrentGameAttribute, Process.GetCurrentProcess().ProcessName, MelonUtils.GameVersion, BuildInfo.VersionNumber, MelonUtils.HashCode, MelonUtils.CurrentPlatform, MelonUtils.CurrentDomain); - } + MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Game Versions:"); - public static void PrintIncompatibilities(Incompatibility[] incompatibilities, MelonBase melon) + foreach (var g in melon.SupportedGameVersions) + MelonLogger.MsgDirect($" - {g.Version}"); + } + if (incompatibilities.Contains(Incompatibility.ProcessName)) { - if (incompatibilities == null || incompatibilities.Length == 0) - return; + MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Process Names:"); - MelonLogger.WriteLine(Color.Red); - MelonLogger.MsgDirect(Color.DarkRed, $"'{melon.Info.Name} v{melon.Info.Version}' is incompatible:"); - if (incompatibilities.Contains(Incompatibility.Game)) - { - MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Games:"); - - foreach (var g in melon.Games) - MelonLogger.MsgDirect($" - '{g.Name}' by {g.Developer}"); - } - if (incompatibilities.Contains(Incompatibility.GameVersion)) - { - MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Game Versions:"); + foreach (var p in melon.SupportedProcesses) + MelonLogger.MsgDirect($" - '{p.EXE_Name}'"); + } + if (incompatibilities.Contains(Incompatibility.Platform)) + { + MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Platforms:"); - foreach (var g in melon.SupportedGameVersions) - MelonLogger.MsgDirect($" - {g.Version}"); - } - if (incompatibilities.Contains(Incompatibility.ProcessName)) - { - MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Process Names:"); + foreach (var p in melon.SupportedPlatforms.Platforms) + MelonLogger.MsgDirect($" - {p}"); + } + if (incompatibilities.Contains(Incompatibility.Domain)) + { + MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Domain:"); + MelonLogger.MsgDirect($" - {melon.SupportedDomain.Domain}"); + } + if (incompatibilities.Contains(Incompatibility.MLVersion)) + { + MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following MelonLoader Versions:"); + MelonLogger.MsgDirect($" - {melon.SupportedMLVersion.SemVer}{(melon.SupportedMLVersion.IsMinimum ? " or higher" : "")}"); + } + if (incompatibilities.Contains(Incompatibility.MLBuild)) + { + MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following MelonLoader Build Hash Codes:"); + MelonLogger.MsgDirect($" - {melon.SupportedMLBuild.HashCode}"); + } - foreach (var p in melon.SupportedProcesses) - MelonLogger.MsgDirect($" - '{p.EXE_Name}'"); - } - if (incompatibilities.Contains(Incompatibility.Platform)) - { - MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Platforms:"); + MelonLogger.WriteLine(Color.Red); + MelonLogger.WriteSpacer(); + } - foreach (var p in melon.SupportedPlatforms.Platforms) - MelonLogger.MsgDirect($" - {p}"); - } - if (incompatibilities.Contains(Incompatibility.Domain)) - { - MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Domain:"); - MelonLogger.MsgDirect($" - {melon.SupportedDomain.Domain}"); - } - if (incompatibilities.Contains(Incompatibility.MLVersion)) - { - MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following MelonLoader Versions:"); - MelonLogger.MsgDirect($" - {melon.SupportedMLVersion.SemVer}{(melon.SupportedMLVersion.IsMinimum ? " or higher" : "")}"); - } - if (incompatibilities.Contains(Incompatibility.MLBuild)) - { - MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following MelonLoader Build Hash Codes:"); - MelonLogger.MsgDirect($" - {melon.SupportedMLBuild.HashCode}"); - } + /// + /// Registers the Melon. + /// + public bool Register() + { + if (Registered) + return false; - MelonLogger.WriteLine(Color.Red); - MelonLogger.WriteSpacer(); + if (FindMelon(Info.Name, Info.Author) != null) + { + MelonLogger.Warning($"Failed to register {MelonTypeName} '{Location}': A Melon with the same Name and Author is already registered!"); + return false; } - /// - /// Registers the Melon. - /// - public bool Register() + var comp = FindIncompatiblitiesFromContext(); + if (comp.Length != 0) { - if (Registered) - return false; - - if (FindMelon(Info.Name, Info.Author) != null) - { - MelonLogger.Warning($"Failed to register {MelonTypeName} '{Location}': A Melon with the same Name and Author is already registered!"); - return false; - } + PrintIncompatibilities(comp, this); + return false; + } - var comp = FindIncompatiblitiesFromContext(); - if (comp.Length != 0) - { - PrintIncompatibilities(comp, this); - return false; - } + OnMelonInitializing.Invoke(this); - OnMelonInitializing.Invoke(this); + LoggerInstance ??= new MelonLogger.Instance(string.IsNullOrEmpty(ID) ? Info.Name : $"{ID}:{Info.Name}", ConsoleColor); + HarmonyInstance ??= new HarmonyLib.Harmony($"{Assembly.FullName}:{Info.Name}"); - LoggerInstance ??= new MelonLogger.Instance(string.IsNullOrEmpty(ID) ? Info.Name : $"{ID}:{Info.Name}", ConsoleColor); - HarmonyInstance ??= new HarmonyLib.Harmony($"{Assembly.FullName}:{Info.Name}"); + Registered = true; // this has to be true before the melon can subscribe to any events + RegisterCallbacks(); - Registered = true; // this has to be true before the melon can subscribe to any events - RegisterCallbacks(); + try + { + OnEarlyInitializeMelon(); + } + catch (Exception ex) + { + MelonLogger.Error($"Failed to register {MelonTypeName} '{Location}': Melon failed to initialize!"); + MelonLogger.Error(ex.ToString()); + Registered = false; + return false; + } - try - { - OnEarlyInitializeMelon(); - } - catch (Exception ex) - { - MelonLogger.Error($"Failed to register {MelonTypeName} '{Location}': Melon failed to initialize!"); - MelonLogger.Error(ex.ToString()); - Registered = false; - return false; - } + if (!RegisterInternal()) + return false; - if (!RegisterInternal()) - return false; + _registeredMelons.Add(this); - _registeredMelons.Add(this); + PrintLoadInfo(); - PrintLoadInfo(); + OnRegister.Invoke(); + OnMelonRegistered.Invoke(this); - OnRegister.Invoke(); - OnMelonRegistered.Invoke(this); + if (MelonEvents.OnApplicationStart.Disposed) + LoaderInitialized(); + else + MelonEvents.OnApplicationStart.Subscribe(LoaderInitialized, Priority, true); - if (MelonEvents.OnApplicationStart.Disposed) - LoaderInitialized(); - else - MelonEvents.OnApplicationStart.Subscribe(LoaderInitialized, Priority, true); + if (MelonEvents.OnApplicationLateStart.Disposed) + OnLateInitializeMelon(); + else + MelonEvents.OnApplicationLateStart.Subscribe(OnLateInitializeMelon, Priority, true); - if (MelonEvents.OnApplicationLateStart.Disposed) - OnLateInitializeMelon(); - else - MelonEvents.OnApplicationLateStart.Subscribe(OnLateInitializeMelon, Priority, true); + return true; + } - return true; - } + private void HarmonyInit() + { + if (!MelonAssembly.HarmonyDontPatchAll) + HarmonyInstance.PatchAll(MelonAssembly.Assembly); + } - private void HarmonyInit() + private void LoaderInitialized() + { + try { - if (!MelonAssembly.HarmonyDontPatchAll) - HarmonyInstance.PatchAll(MelonAssembly.Assembly); + OnInitializeMelon(); } - - private void LoaderInitialized() + catch (Exception ex) { - try - { - OnInitializeMelon(); - } - catch (Exception ex) - { - LoggerInstance.Error(ex); - } + LoggerInstance.Error(ex); } + } - protected private virtual bool RegisterInternal() - { - return true; - } + private protected virtual bool RegisterInternal() + { + return true; + } - protected private virtual void UnregisterInternal() { } + private protected virtual void UnregisterInternal() { } - protected private virtual void RegisterCallbacks() - { - MelonEvents.OnApplicationQuit.Subscribe(OnApplicationQuit, Priority); - MelonEvents.OnUpdate.Subscribe(OnUpdate, Priority); - MelonEvents.OnLateUpdate.Subscribe(OnLateUpdate, Priority); - MelonEvents.OnGUI.Subscribe(OnGUI, Priority); - MelonEvents.OnFixedUpdate.Subscribe(OnFixedUpdate, Priority); - MelonEvents.OnApplicationLateStart.Subscribe(OnApplicationLateStart, Priority); - - MelonPreferences.OnPreferencesLoaded.Subscribe(PrefsLoaded, Priority); - MelonPreferences.OnPreferencesSaved.Subscribe(PrefsSaved, Priority); - } + private protected virtual void RegisterCallbacks() + { + MelonEvents.OnApplicationQuit.Subscribe(OnApplicationQuit, Priority); + MelonEvents.OnUpdate.Subscribe(OnUpdate, Priority); + MelonEvents.OnLateUpdate.Subscribe(OnLateUpdate, Priority); + MelonEvents.OnGUI.Subscribe(OnGUI, Priority); + MelonEvents.OnFixedUpdate.Subscribe(OnFixedUpdate, Priority); + MelonEvents.OnApplicationLateStart.Subscribe(OnApplicationLateStart, Priority); + + MelonPreferences.OnPreferencesLoaded.Subscribe(PrefsLoaded, Priority); + MelonPreferences.OnPreferencesSaved.Subscribe(PrefsSaved, Priority); + } - private void PrefsSaved(string path) - { - OnPreferencesSaved(path); - OnPreferencesSaved(); - OnModSettingsApplied(); - } + private void PrefsSaved(string path) + { + OnPreferencesSaved(path); + OnPreferencesSaved(); + OnModSettingsApplied(); + } - private void PrefsLoaded(string path) + private void PrefsLoaded(string path) + { + OnPreferencesLoaded(path); + OnPreferencesLoaded(); + } + + /// + /// Tries to find a registered Melon that matches the given Info. + /// + public static MelonBase FindMelon(string melonName, string melonAuthor) + { + return _registeredMelons.Find(x => x.Info.Name == melonName && x.Info.Author == melonAuthor); + } + + /// + /// Unregisters the Melon and all other Melons located in the same Assembly. + /// This only unsubscribes the Melons from all Callbacks/s and unpatches all Methods that were patched by Harmony, but doesn't actually unload the whole Assembly. + /// + public void Unregister(string reason = null, bool silent = false) + { + if (!Registered) + return; + + MelonAssembly.UnregisterMelons(reason, silent); + } + + internal void UnregisterInstance(string reason, bool silent) + { + if (!Registered) + return; + + try { - OnPreferencesLoaded(path); - OnPreferencesLoaded(); + OnDeinitializeMelon(); } - - /// - /// Tries to find a registered Melon that matches the given Info. - /// - public static MelonBase FindMelon(string melonName, string melonAuthor) + catch (Exception ex) { - return _registeredMelons.Find(x => x.Info.Name == melonName && x.Info.Author == melonAuthor); + MelonLogger.Error($"Failed to properly unregister {MelonTypeName} '{Location}': Melon failed to deinitialize!"); + MelonLogger.Error(ex.ToString()); } - /// - /// Unregisters the Melon and all other Melons located in the same Assembly. - /// This only unsubscribes the Melons from all Callbacks/s and unpatches all Methods that were patched by Harmony, but doesn't actually unload the whole Assembly. - /// - public void Unregister(string reason = null, bool silent = false) - { - if (!Registered) - return; + UnregisterInternal(); - MelonAssembly.UnregisterMelons(reason, silent); - } + _registeredMelons.Remove(this); + HarmonyInstance.UnpatchSelf(); + Registered = false; - internal void UnregisterInstance(string reason, bool silent) - { - if (!Registered) - return; + if (!silent) + PrintUnloadInfo(reason); - try - { - OnDeinitializeMelon(); - } - catch (Exception ex) - { - MelonLogger.Error($"Failed to properly unregister {MelonTypeName} '{Location}': Melon failed to deinitialize!"); - MelonLogger.Error(ex.ToString()); - } + OnUnregister.Invoke(); + OnMelonUnregistered.Invoke(this); + } - UnregisterInternal(); + private void PrintLoadInfo() + { + MelonLogger.WriteLine(Color.DarkGreen); - _registeredMelons.Remove(this); - HarmonyInstance.UnpatchSelf(); - Registered = false; + MelonLogger.PrintModName(ConsoleColor, AuthorConsoleColor, Info.Name, Info.Author, AdditionalCredits?.Credits, Info.Version, ID); + MelonLogger.MsgDirect(Color.DarkGray, $"Assembly: {Path.GetFileName(MelonAssembly.Location)}"); - if (!silent) - PrintUnloadInfo(reason); + MelonLogger.WriteLine(Color.DarkGreen); + } - OnUnregister.Invoke(); - OnMelonUnregistered.Invoke(this); - } + private void PrintUnloadInfo(string reason) + { + MelonLogger.WriteLine(Color.DarkRed); - private void PrintLoadInfo() - { - MelonLogger.WriteLine(Color.DarkGreen); - - MelonLogger.PrintModName(ConsoleColor, AuthorConsoleColor, Info.Name, Info.Author, AdditionalCredits?.Credits, Info.Version, ID); - MelonLogger.MsgDirect(Color.DarkGray, $"Assembly: {Path.GetFileName(MelonAssembly.Location)}"); + MelonLogger.MsgDirect(Color.DarkGray, MelonTypeName + " deinitialized:"); + MelonLogger.PrintModName(ConsoleColor, AuthorConsoleColor, Info.Name, Info.Author, AdditionalCredits?.Credits, Info.Version, ID); - MelonLogger.WriteLine(Color.DarkGreen); + if (!string.IsNullOrEmpty(reason)) + { + MelonLogger.MsgDirect(string.Empty); + MelonLogger.MsgDirect($"Reason: '{reason}'"); } - private void PrintUnloadInfo(string reason) - { - MelonLogger.WriteLine(Color.DarkRed); + MelonLogger.WriteLine(Color.DarkRed); + } + + public static void ExecuteAll(LemonAction func, bool unregisterOnFail = false, string unregistrationReason = null) + { + ExecuteList(func, _registeredMelons, unregisterOnFail, unregistrationReason); + } + + public static void ExecuteList(LemonAction func, List melons, bool unregisterOnFail = false, string unregistrationReason = null) where T : MelonBase + { + var failedMelons = unregisterOnFail ? new List() : null; - MelonLogger.MsgDirect(Color.DarkGray, MelonTypeName + " deinitialized:"); - MelonLogger.PrintModName(ConsoleColor, AuthorConsoleColor, Info.Name, Info.Author, AdditionalCredits?.Credits, Info.Version, ID); + LemonEnumerator enumerator = new(melons.ToArray()); + while (enumerator.MoveNext()) + { + var melon = enumerator.Current; + if (!melon.Registered) + continue; - if (!string.IsNullOrEmpty(reason)) + try { - MelonLogger.MsgDirect(string.Empty); - MelonLogger.MsgDirect($"Reason: '{reason}'"); + func(melon); + } + catch (Exception ex) + { + melon.LoggerInstance.Error(ex.ToString()); + if (unregisterOnFail) + failedMelons.Add(melon); } - - MelonLogger.WriteLine(Color.DarkRed); } - public static void ExecuteAll(LemonAction func, bool unregisterOnFail = false, string unregistrationReason = null) + if (unregisterOnFail) { - ExecuteList(func, _registeredMelons, unregisterOnFail, unregistrationReason); + foreach (var m in failedMelons) + m.Unregister(unregistrationReason); } + } - public static void ExecuteList(LemonAction func, List melons, bool unregisterOnFail = false, string unregistrationReason = null) where T : MelonBase + public static void SendMessageAll(string name, params object[] arguments) + { + LemonEnumerator enumerator = new(_registeredMelons.ToArray()); + while (enumerator.MoveNext()) { - var failedMelons = (unregisterOnFail ? new List() : null); + var melon = enumerator.Current; + if (!melon.Registered) + continue; - LemonEnumerator enumerator = new(melons.ToArray()); - while (enumerator.MoveNext()) - { - var melon = enumerator.Current; - if (!melon.Registered) - continue; - - try { func(melon); } - catch (Exception ex) - { - melon.LoggerInstance.Error(ex.ToString()); - if (unregisterOnFail) - failedMelons.Add(melon); - } - } - - if (unregisterOnFail) + try { - foreach (var m in failedMelons) - m.Unregister(unregistrationReason); + melon.SendMessage(name, arguments); } - } - - public static void SendMessageAll(string name, params object[] arguments) - { - LemonEnumerator enumerator = new(_registeredMelons.ToArray()); - while (enumerator.MoveNext()) + catch (Exception ex) { - var melon = enumerator.Current; - if (!melon.Registered) - continue; - - try { melon.SendMessage(name, arguments); } - catch (Exception ex) { melon.LoggerInstance.Error(ex.ToString()); } + melon.LoggerInstance.Error(ex.ToString()); } } + } - public object SendMessage(string name, params object[] arguments) - { - var msg = Info.SystemType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - - if (msg == null) - return null; + public object SendMessage(string name, params object[] arguments) + { + var msg = Info.SystemType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - return msg.Invoke(msg.IsStatic ? null : this, arguments); - } - #endregion + return msg == null ? null : msg.Invoke(msg.IsStatic ? null : this, arguments); + } + #endregion - #region Obsolete Members + #region Obsolete Members - private Harmony.HarmonyInstance _OldHarmonyInstance; + private Harmony.HarmonyInstance _OldHarmonyInstance; - [Obsolete("Please use either the OnLateInitializeMelon callback, or the 'MelonEvents::OnApplicationLateStart' event instead.")] - public virtual void OnApplicationLateStart() { } + [Obsolete("Please use either the OnLateInitializeMelon callback, or the 'MelonEvents::OnApplicationLateStart' event instead.")] + public virtual void OnApplicationLateStart() { } - [Obsolete("For mods, use OnInitializeMelon instead. For plugins, use OnPreModsLoaded instead.")] - public virtual void OnApplicationStart() { } + [Obsolete("For mods, use OnInitializeMelon instead. For plugins, use OnPreModsLoaded instead.")] + public virtual void OnApplicationStart() { } - [Obsolete("Please use OnPreferencesSaved instead.")] - public virtual void OnModSettingsApplied() { } + [Obsolete("Please use OnPreferencesSaved instead.")] + public virtual void OnModSettingsApplied() { } - [Obsolete("Please use HarmonyInstance instead.")] + [Obsolete("Please use HarmonyInstance instead.")] #pragma warning disable IDE1006 // Naming Styles - public Harmony.HarmonyInstance harmonyInstance { get { _OldHarmonyInstance ??= new Harmony.HarmonyInstance(HarmonyInstance.Id); return _OldHarmonyInstance; } } + public Harmony.HarmonyInstance harmonyInstance + { + get + { + _OldHarmonyInstance ??= new Harmony.HarmonyInstance(HarmonyInstance.Id); + return _OldHarmonyInstance; + } + } #pragma warning restore IDE1006 // Naming Styles - [Obsolete("Please use HarmonyInstance instead.")] - public Harmony.HarmonyInstance Harmony { get { _OldHarmonyInstance ??= new Harmony.HarmonyInstance(HarmonyInstance.Id); return _OldHarmonyInstance; } } - - [Obsolete("Please use MelonAssembly.Assembly instead.")] - public Assembly Assembly => MelonAssembly.Assembly; + [Obsolete("Please use HarmonyInstance instead.")] + public Harmony.HarmonyInstance Harmony + { + get + { + _OldHarmonyInstance ??= new Harmony.HarmonyInstance(HarmonyInstance.Id); + return _OldHarmonyInstance; + } + } - [Obsolete("Please use MelonAssembly.HarmonyDontPatchAll instead.")] - public bool HarmonyDontPatchAll => MelonAssembly.HarmonyDontPatchAll; + [Obsolete("Please use MelonAssembly.Assembly instead.")] + public Assembly Assembly => MelonAssembly.Assembly; - [Obsolete("Please use MelonAssembly.Hash instead.")] - public string Hash => MelonAssembly.Hash; + [Obsolete("Please use MelonAssembly.HarmonyDontPatchAll instead.")] + public bool HarmonyDontPatchAll => MelonAssembly.HarmonyDontPatchAll; - [Obsolete("Please use MelonAssembly.Location instead.")] - public string Location => MelonAssembly.Location; + [Obsolete("Please use MelonAssembly.Hash instead.")] + public string Hash => MelonAssembly.Hash; - #endregion + [Obsolete("Please use MelonAssembly.Location instead.")] + public string Location => MelonAssembly.Location; + #endregion - public enum Incompatibility - { - MLVersion, - MLBuild, - Game, - GameVersion, - ProcessName, - Domain, - Platform - } + public enum Incompatibility + { + MLVersion, + MLBuild, + Game, + GameVersion, + ProcessName, + Domain, + Platform } } diff --git a/MelonLoader/MelonColorAttribute.cs b/MelonLoader/MelonColorAttribute.cs index 46af0637d..ae7cb8276 100644 --- a/MelonLoader/MelonColorAttribute.cs +++ b/MelonLoader/MelonColorAttribute.cs @@ -1,35 +1,34 @@ -using System; +using MelonLoader.Utils; +using System; using System.Drawing; -using MelonLoader.Utils; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonColorAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonColorAttribute : Attribute + /// + /// Color of the Melon. + /// + [Obsolete("Color is obsolete. Use DrawingColor for full Color support.")] + public ConsoleColor Color { - /// - /// Color of the Melon. - /// - [Obsolete("Color is obsolete. Use DrawingColor for full Color support.")] - public ConsoleColor Color - { - get => LoggerUtils.DrawingColorToConsoleColor(DrawingColor); - set => DrawingColor = LoggerUtils.ConsoleColorToDrawingColor(value); - } + get => LoggerUtils.DrawingColorToConsoleColor(DrawingColor); + set => DrawingColor = LoggerUtils.ConsoleColorToDrawingColor(value); + } - /// - /// Color of the Author Log. - /// - public Color DrawingColor { get; internal set; } + /// + /// Color of the Author Log. + /// + public Color DrawingColor { get; internal set; } - public MelonColorAttribute() - => DrawingColor = MelonLogger.DefaultTextColor; + public MelonColorAttribute() + => DrawingColor = MelonLogger.DefaultTextColor; - [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead.")] - public MelonColorAttribute(ConsoleColor color) - => Color = ((color == ConsoleColor.Black) ? LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultMelonColor) : color); + [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead.")] + public MelonColorAttribute(ConsoleColor color) + => Color = (color == ConsoleColor.Black) ? LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultMelonColor) : color; - public MelonColorAttribute(int alpha, int red, int green, int blue) - => DrawingColor = System.Drawing.Color.FromArgb(alpha, red, green, blue); - } + public MelonColorAttribute(int alpha, int red, int green, int blue) + => DrawingColor = System.Drawing.Color.FromArgb(alpha, red, green, blue); } \ No newline at end of file diff --git a/MelonLoader/MelonCompatibilityLayer.cs b/MelonLoader/MelonCompatibilityLayer.cs index be5379988..054058e58 100644 --- a/MelonLoader/MelonCompatibilityLayer.cs +++ b/MelonLoader/MelonCompatibilityLayer.cs @@ -1,84 +1,83 @@ using MelonLoader.Modules; +using MelonLoader.Utils; using System; using System.Collections.Generic; using System.IO; -using MelonLoader.Utils; -namespace MelonLoader +namespace MelonLoader; + +public static class MelonCompatibilityLayer { - public static class MelonCompatibilityLayer + public static string baseDirectory = $"{MelonEnvironment.MelonBaseDirectory}{Path.DirectorySeparatorChar}MelonLoader{Path.DirectorySeparatorChar}Dependencies{Path.DirectorySeparatorChar}CompatibilityLayers"; + + private static readonly List layers = + [ + // Illusion Plugin Architecture + new MelonModule.Info(Path.Combine(baseDirectory, "IPA.dll"), MelonUtils.IsGameIl2Cpp), + ]; + + private static void CheckGameLayerWithPlatform(string name, Func shouldBeIgnored) { - public static string baseDirectory = $"{MelonEnvironment.MelonBaseDirectory}{Path.DirectorySeparatorChar}MelonLoader{Path.DirectorySeparatorChar}Dependencies{Path.DirectorySeparatorChar}CompatibilityLayers"; + if (string.IsNullOrEmpty(name)) + return; - private static List layers = new List() - { - // Illusion Plugin Architecture - new MelonModule.Info(Path.Combine(baseDirectory, "IPA.dll"), MelonUtils.IsGameIl2Cpp), - }; - - private static void CheckGameLayerWithPlatform(string name, Func shouldBeIgnored) + var nameNoSpaces = name.Replace(' ', '_'); + foreach (var file in Directory.GetFiles(baseDirectory)) { - if (string.IsNullOrEmpty(name)) - return; - - string nameNoSpaces = name.Replace(' ', '_'); - foreach (var file in Directory.GetFiles(baseDirectory)) - { - string fileName = Path.GetFileNameWithoutExtension(file); - if (string.IsNullOrEmpty(fileName)) - continue; - if (fileName.StartsWith(nameNoSpaces)) - layers.Add(new MelonModule.Info(file, shouldBeIgnored)); - } + var fileName = Path.GetFileNameWithoutExtension(file); + if (string.IsNullOrEmpty(fileName)) + continue; + if (fileName.StartsWith(nameNoSpaces)) + layers.Add(new MelonModule.Info(file, shouldBeIgnored)); } + } + + private static void CheckGameLayer(string name) + { + if (string.IsNullOrEmpty(name)) + return; + + CheckGameLayerWithPlatform(name, () => false); + CheckGameLayerWithPlatform($"{name}_Mono", () => MelonUtils.IsGameIl2Cpp()); + CheckGameLayerWithPlatform($"{name}_Il2Cpp", () => !MelonUtils.IsGameIl2Cpp()); - private static void CheckGameLayer(string name) + var spaceIndex = name.IndexOf(' '); + if (spaceIndex > 0) { + name = name[..(spaceIndex - 1)]; if (string.IsNullOrEmpty(name)) return; CheckGameLayerWithPlatform(name, () => false); CheckGameLayerWithPlatform($"{name}_Mono", () => MelonUtils.IsGameIl2Cpp()); CheckGameLayerWithPlatform($"{name}_Il2Cpp", () => !MelonUtils.IsGameIl2Cpp()); - - int spaceIndex = name.IndexOf(' '); - if (spaceIndex > 0) - { - name = name.Substring(0, spaceIndex - 1); - if (string.IsNullOrEmpty(name)) - return; - - CheckGameLayerWithPlatform(name, () => false); - CheckGameLayerWithPlatform($"{name}_Mono", () => MelonUtils.IsGameIl2Cpp()); - CheckGameLayerWithPlatform($"{name}_Il2Cpp", () => !MelonUtils.IsGameIl2Cpp()); - } } + } - internal static void LoadModules() - { - if (!Directory.Exists(baseDirectory)) - return; + internal static void LoadModules() + { + if (!Directory.Exists(baseDirectory)) + return; - CheckGameLayer(InternalUtils.UnityInformationHandler.GameName); - CheckGameLayer(InternalUtils.UnityInformationHandler.GameDeveloper); - CheckGameLayer($"{InternalUtils.UnityInformationHandler.GameDeveloper}_{InternalUtils.UnityInformationHandler.GameName}"); + CheckGameLayer(InternalUtils.UnityInformationHandler.GameName); + CheckGameLayer(InternalUtils.UnityInformationHandler.GameDeveloper); + CheckGameLayer($"{InternalUtils.UnityInformationHandler.GameDeveloper}_{InternalUtils.UnityInformationHandler.GameName}"); - foreach (var m in layers) - { - if ((m.shouldBeIgnored != null) - && m.shouldBeIgnored()) - continue; + foreach (var m in layers) + { + if ((m.shouldBeIgnored != null) + && m.shouldBeIgnored()) + continue; - MelonDebug.Msg($"Loading MelonModule '{m.fullPath}'"); - m.moduleGC = MelonModule.Load(m); - } + MelonDebug.Msg($"Loading MelonModule '{m.fullPath}'"); + m.moduleGC = MelonModule.Load(m); + } - foreach (var file in Directory.GetFiles(baseDirectory)) - { - string fileName = Path.GetFileName(file); - if (layers.Find(x => Path.GetFileName(x.fullPath).Equals(fileName)) == null) - File.Delete(file); - } + foreach (var file in Directory.GetFiles(baseDirectory)) + { + var fileName = Path.GetFileName(file); + if (layers.Find(x => Path.GetFileName(x.fullPath).Equals(fileName)) == null) + File.Delete(file); } } } \ No newline at end of file diff --git a/MelonLoader/MelonCoroutines.cs b/MelonLoader/MelonCoroutines.cs index 793467896..1ff120432 100644 --- a/MelonLoader/MelonCoroutines.cs +++ b/MelonLoader/MelonCoroutines.cs @@ -1,32 +1,31 @@ using System; using System.Collections; -namespace MelonLoader +namespace MelonLoader; + +public class MelonCoroutines { - public class MelonCoroutines + /// + /// Start a new coroutine.
+ /// Coroutines are called at the end of the game Update loops. + ///
+ /// The target routine + /// An object that can be passed to Stop to stop this coroutine + public static object Start(IEnumerator routine) { - /// - /// Start a new coroutine.
- /// Coroutines are called at the end of the game Update loops. - ///
- /// The target routine - /// An object that can be passed to Stop to stop this coroutine - public static object Start(IEnumerator routine) - { - if (SupportModule.Interface == null) - throw new NotSupportedException("Support module must be initialized before starting coroutines"); - return SupportModule.Interface.StartCoroutine(routine); - } + return SupportModule.Interface == null + ? throw new NotSupportedException("Support module must be initialized before starting coroutines") + : SupportModule.Interface.StartCoroutine(routine); + } - /// - /// Stop a currently running coroutine - /// - /// The coroutine to stop - public static void Stop(object coroutineToken) - { - if (SupportModule.Interface == null) - throw new NotSupportedException("Support module must be initialized before starting coroutines"); - SupportModule.Interface.StopCoroutine(coroutineToken); - } + /// + /// Stop a currently running coroutine + /// + /// The coroutine to stop + public static void Stop(object coroutineToken) + { + if (SupportModule.Interface == null) + throw new NotSupportedException("Support module must be initialized before starting coroutines"); + SupportModule.Interface.StopCoroutine(coroutineToken); } } \ No newline at end of file diff --git a/MelonLoader/MelonDebug.cs b/MelonLoader/MelonDebug.cs index 0408e8fa6..f6ea8afb2 100644 --- a/MelonLoader/MelonDebug.cs +++ b/MelonLoader/MelonDebug.cs @@ -1,48 +1,47 @@ -using System; +using MelonLoader.Utils; +using System; using System.Drawing; -using MelonLoader.Utils; -namespace MelonLoader +namespace MelonLoader; + +public static class MelonDebug { - public static class MelonDebug + public static void Msg(object obj) + { + if (!IsEnabled()) + return; + MelonLogger.PassLogMsg(MelonLogger.DefaultTextColor, obj.ToString(), Color.CornflowerBlue, "DEBUG"); + MsgCallbackHandler?.Invoke(LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultTextColor), obj.ToString()); + } + + public static void Msg(string txt) + { + if (!IsEnabled()) + return; + MelonLogger.PassLogMsg(MelonLogger.DefaultTextColor, txt, Color.CornflowerBlue, "DEBUG"); + MsgCallbackHandler?.Invoke(LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultTextColor), txt); + } + + public static void Msg(string txt, params object[] args) { - public static void Msg(object obj) - { - if (!IsEnabled()) - return; - MelonLogger.PassLogMsg(MelonLogger.DefaultTextColor, obj.ToString(), Color.CornflowerBlue, "DEBUG"); - MsgCallbackHandler?.Invoke(LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultTextColor), obj.ToString()); - } - - public static void Msg(string txt) - { - if (!IsEnabled()) - return; - MelonLogger.PassLogMsg(MelonLogger.DefaultTextColor, txt, Color.CornflowerBlue, "DEBUG"); - MsgCallbackHandler?.Invoke(LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultTextColor), txt); - } - - public static void Msg(string txt, params object[] args) - { - if (!IsEnabled()) - return; - MelonLogger.PassLogMsg(MelonLogger.DefaultTextColor, string.Format(txt, args), Color.CornflowerBlue, "DEBUG"); - MsgCallbackHandler?.Invoke(LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultTextColor), string.Format(txt, args)); - } - - public static void Error(string txt) - { - if (!IsEnabled()) - return; - MelonLogger.PassLogError(txt, "DEBUG", false); - ErrorCallbackHandler?.Invoke(txt); - } - - public static event Action MsgCallbackHandler; - - public static event Action ErrorCallbackHandler; - - public static bool IsEnabled() - => LoaderConfig.Current.Loader.DebugMode; + if (!IsEnabled()) + return; + MelonLogger.PassLogMsg(MelonLogger.DefaultTextColor, string.Format(txt, args), Color.CornflowerBlue, "DEBUG"); + MsgCallbackHandler?.Invoke(LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultTextColor), string.Format(txt, args)); } + + public static void Error(string txt) + { + if (!IsEnabled()) + return; + MelonLogger.PassLogError(txt, "DEBUG", false); + ErrorCallbackHandler?.Invoke(txt); + } + + public static event Action MsgCallbackHandler; + + public static event Action ErrorCallbackHandler; + + public static bool IsEnabled() + => LoaderConfig.Current.Loader.DebugMode; } \ No newline at end of file diff --git a/MelonLoader/MelonEvent.cs b/MelonLoader/MelonEvent.cs index 5a4ec7926..6a861af5c 100644 --- a/MelonLoader/MelonEvent.cs +++ b/MelonLoader/MelonEvent.cs @@ -2,246 +2,248 @@ using System.Collections.Generic; using System.Reflection; -namespace MelonLoader +namespace MelonLoader; + +public abstract class MelonEventBase where T : Delegate { - public abstract class MelonEventBase where T : Delegate - { - private readonly List> actions = new(); - private MelonAction[] cachedActionsArray = new MelonAction[0]; - public readonly bool oneTimeUse; + private readonly List> actions = []; + private MelonAction[] cachedActionsArray = new MelonAction[0]; + public readonly bool oneTimeUse; - public bool Disposed { get; private set; } + public bool Disposed { get; private set; } - public MelonEventBase(bool oneTimeUse = false) - { - this.oneTimeUse = oneTimeUse; - } + public MelonEventBase(bool oneTimeUse = false) + { + this.oneTimeUse = oneTimeUse; + } - public bool CheckIfSubscribed(MethodInfo method, object obj = null) + public bool CheckIfSubscribed(MethodInfo method, object obj = null) + { + lock (actions) { - lock (actions) - { - return actions.Exists(x => x.del.Method == method && (obj == null || x.del.Target == obj)); - } + return actions.Exists(x => x.del.Method == method && (obj == null || x.del.Target == obj)); } + } - public void Subscribe(T action, int priority = 0, bool unsubscribeOnFirstInvocation = false) - { - if (Disposed) - return; + public void Subscribe(T action, int priority = 0, bool unsubscribeOnFirstInvocation = false) + { + if (Disposed) + return; - lock (actions) + lock (actions) + { + var acts = MelonAction.Get(action, priority, unsubscribeOnFirstInvocation); + foreach (var a in acts) { - var acts = MelonAction.Get(action, priority, unsubscribeOnFirstInvocation); - foreach (var a in acts) - { - if (CheckIfSubscribed(a.del.Method, a.del.Target)) - continue; + if (CheckIfSubscribed(a.del.Method, a.del.Target)) + continue; - if (a.melonAssembly != null) - { - MelonDebug.Msg($"MelonAssembly '{a.melonAssembly.Assembly.GetName().Name}' subscribed with {a.del.Method.Name}"); - a.melonAssembly.OnUnregister.Subscribe(() => Unsubscribe(a.del), unsubscribeOnFirstInvocation: true); - } + if (a.melonAssembly != null) + { + MelonDebug.Msg($"MelonAssembly '{a.melonAssembly.Assembly.GetName().Name}' subscribed with {a.del.Method.Name}"); + a.melonAssembly.OnUnregister.Subscribe(() => Unsubscribe(a.del), unsubscribeOnFirstInvocation: true); + } - for (var b = 0; b < actions.Count; b++) + for (var b = 0; b < actions.Count; b++) + { + var act = actions[b]; + if (a.priority < act.priority) { - var act = actions[b]; - if (a.priority < act.priority) - { - actions.Insert(b, a); - UpdateEnumerator(); - return; - } + actions.Insert(b, a); + UpdateEnumerator(); + return; } - - actions.Add(a); - UpdateEnumerator(); } - } - } - public void Unsubscribe(T action) - { - foreach (var inv in action.GetInvocationList()) - { - Unsubscribe(inv.Method, inv.Target); + actions.Add(a); + UpdateEnumerator(); } } + } - public void UnsubscribeAll() + public void Unsubscribe(T action) + { + foreach (var inv in action.GetInvocationList()) { - lock (actions) - actions.Clear(); - - UpdateEnumerator(); + Unsubscribe(inv.Method, inv.Target); } + } - public void Unsubscribe(MethodInfo method, object obj = null) - { - if (Disposed) - return; - - lock (actions) - { - if (method.IsStatic) - obj = null; - - var any = false; - for (var a = 0; a < actions.Count; a++) - { - var act = actions[a]; - if (act.del.Method != method || (obj != null && act.del.Target != obj)) - continue; - - any = true; - actions.RemoveAt(a); - if (act.melonAssembly != null) - MelonDebug.Msg($"MelonAssembly '{act.melonAssembly.Assembly.GetName().Name}' unsubscribed with {act.del.Method.Name}"); - } - - if (any) - UpdateEnumerator(); - } - } + public void UnsubscribeAll() + { + lock (actions) + actions.Clear(); - private void UpdateEnumerator() - { - cachedActionsArray = actions.ToArray(); - } + UpdateEnumerator(); + } - public class MelonEventSubscriber - { - public T del; - public bool unsubscribeOnFirstInvocation; - public int priority; - public MelonAssembly melonAssembly; - } - public MelonEventSubscriber[] GetSubscribers() - { - List allSubs = new List(); - foreach (var act in actions) - allSubs.Add(new MelonEventSubscriber - { - del = act.del, - unsubscribeOnFirstInvocation = act.unsubscribeOnFirstInvocation, - priority = act.priority, - melonAssembly = act.melonAssembly - }); - return allSubs.ToArray(); - } + public void Unsubscribe(MethodInfo method, object obj = null) + { + if (Disposed) + return; - protected void Invoke(Action delegateInvoker) + lock (actions) { - if (Disposed) - return; + if (method.IsStatic) + obj = null; - var actionsArray = cachedActionsArray; - for (var a = 0; a < actionsArray.Length; a++) + var any = false; + for (var a = 0; a < actions.Count; a++) { - var del = actionsArray[a]; - try { delegateInvoker(del.del); } - catch (Exception ex) - { - if (del.melonAssembly == null || del.melonAssembly.LoadedMelons.Count == 0) - MelonLogger.Error(ex.ToString()); - else - del.melonAssembly.LoadedMelons[0].LoggerInstance.Error(ex.ToString()); - } - - if (del.unsubscribeOnFirstInvocation) - Unsubscribe(del.del); + var act = actions[a]; + if (act.del.Method != method || (obj != null && act.del.Target != obj)) + continue; + + any = true; + actions.RemoveAt(a); + if (act.melonAssembly != null) + MelonDebug.Msg($"MelonAssembly '{act.melonAssembly.Assembly.GetName().Name}' unsubscribed with {act.del.Method.Name}"); } - if (oneTimeUse) - Dispose(); - } - - public void Dispose() - { - UnsubscribeAll(); - Disposed = true; + if (any) + UpdateEnumerator(); } } - #region Param Children - public class MelonEvent : MelonEventBase + private void UpdateEnumerator() { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + cachedActionsArray = actions.ToArray(); + } - public void Invoke() - { - Invoke(x => x()); - } + public class MelonEventSubscriber + { + public T del; + public bool unsubscribeOnFirstInvocation; + public int priority; + public MelonAssembly melonAssembly; } - public class MelonEvent : MelonEventBase> + public MelonEventSubscriber[] GetSubscribers() { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } - - public void Invoke(T1 arg1) - { - Invoke(x => x(arg1)); - } + List allSubs = []; + foreach (var act in actions) + allSubs.Add(new MelonEventSubscriber + { + del = act.del, + unsubscribeOnFirstInvocation = act.unsubscribeOnFirstInvocation, + priority = act.priority, + melonAssembly = act.melonAssembly + }); + return allSubs.ToArray(); } - public class MelonEvent : MelonEventBase> + + protected void Invoke(Action delegateInvoker) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + if (Disposed) + return; - public void Invoke(T1 arg1, T2 arg2) + var actionsArray = cachedActionsArray; + for (var a = 0; a < actionsArray.Length; a++) { - Invoke(x => x(arg1, arg2)); + var del = actionsArray[a]; + try + { + delegateInvoker(del.del); + } + catch (Exception ex) + { + if (del.melonAssembly == null || del.melonAssembly.LoadedMelons.Count == 0) + MelonLogger.Error(ex.ToString()); + else + del.melonAssembly.LoadedMelons[0].LoggerInstance.Error(ex.ToString()); + } + + if (del.unsubscribeOnFirstInvocation) + Unsubscribe(del.del); } + + if (oneTimeUse) + Dispose(); } - public class MelonEvent : MelonEventBase> + + public void Dispose() { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + UnsubscribeAll(); + Disposed = true; + } +} - public void Invoke(T1 arg1, T2 arg2, T3 arg3) - { - Invoke(x => x(arg1, arg2, arg3)); - } +#region Param Children +public class MelonEvent : MelonEventBase +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + + public void Invoke() + { + Invoke(x => x()); } - public class MelonEvent : MelonEventBase> +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + + public void Invoke(T1 arg1) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + Invoke(x => x(arg1)); + } +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } - public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4) - { - Invoke(x => x(arg1, arg2, arg3, arg4)); - } + public void Invoke(T1 arg1, T2 arg2) + { + Invoke(x => x(arg1, arg2)); } - public class MelonEvent : MelonEventBase> +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + + public void Invoke(T1 arg1, T2 arg2, T3 arg3) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + Invoke(x => x(arg1, arg2, arg3)); + } +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } - public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - { - Invoke(x => x(arg1, arg2, arg3, arg4, arg5)); - } + public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + Invoke(x => x(arg1, arg2, arg3, arg4)); } - public class MelonEvent : MelonEventBase> +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + + public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } - public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - { - Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6)); - } + Invoke(x => x(arg1, arg2, arg3, arg4, arg5)); } - public class MelonEvent : MelonEventBase> +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } - public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - { - Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6, arg7)); - } + Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6)); } - public class MelonEvent : MelonEventBase> +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } - public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - { - Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); - } + Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6, arg7)); + } +} +public class MelonEvent : MelonEventBase> +{ + public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } + public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); } - #endregion } +#endregion diff --git a/MelonLoader/MelonEvents.cs b/MelonLoader/MelonEvents.cs index 757a1582c..9497c0b4f 100644 --- a/MelonLoader/MelonEvents.cs +++ b/MelonLoader/MelonEvents.cs @@ -1,99 +1,98 @@ -namespace MelonLoader +namespace MelonLoader; + +public static class MelonEvents { - public static class MelonEvents - { - /// - /// Called after all MelonPlugins are initialized. - /// - public readonly static MelonEvent OnPreInitialization = new(true); - - /// - /// Called after Game Initialization, before OnApplicationStart and before Assembly Generation on Il2Cpp games. - /// - public readonly static MelonEvent OnApplicationEarlyStart = new(true); - - /// - /// Called after all MelonMods are initialized and before the right Support Module is loaded. - /// - public readonly static MelonEvent OnPreSupportModule = new(true); - - /// - /// Called after all MelonLoader components are fully initialized (including all MelonMods). - /// Don't use this event to initialize your Melons anymore! Instead, override . - /// - public readonly static MelonEvent OnApplicationStart = new(true); - - /// - /// Called when the first 'Start' Unity Messages are invoked. - /// - public readonly static MelonEvent OnApplicationLateStart = new(true); - - /// - /// Called before the Application is closed. It is not possible to prevent the game from closing at this point. - /// - public readonly static MelonEvent OnApplicationDefiniteQuit = new(true); - - /// - /// Called on a quit request. It is possible to abort the request in this callback. - /// - public readonly static MelonEvent OnApplicationQuit = new(); - - /// - /// Called once per frame. - /// - public readonly static MelonEvent OnUpdate = new(); - - /// - /// Called every 0.02 seconds, unless Time.fixedDeltaTime has a different Value. It is recommended to do all important Physics calculations inside this Callback. - /// - public readonly static MelonEvent OnFixedUpdate = new(); - - /// - /// Called once per frame, after . - /// - public readonly static MelonEvent OnLateUpdate = new(); - - /// - /// Called at every IMGUI event. Only use this for drawing IMGUI Elements. - /// - public readonly static MelonEvent OnGUI = new(); - - /// - /// Called when a new Scene is loaded. - /// - /// Arguments: - ///
: Build Index of the Scene.
- ///
: Name of the Scene.
- ///
- ///
- public readonly static MelonEvent OnSceneWasLoaded = new(); - - /// - /// Called once a Scene is initialized. - /// - /// Arguments: - ///
: Build Index of the Scene.
- ///
: Name of the Scene.
- ///
- ///
- public readonly static MelonEvent OnSceneWasInitialized = new(); - - /// - /// Called once a Scene unloads. - /// - /// Arguments: - ///
: Build Index of the Scene.
- ///
: Name of the Scene.
- ///
- ///
- public readonly static MelonEvent OnSceneWasUnloaded = new(); - - /// - /// Called before MelonMods are loaded from the Mods folder. - /// - public readonly static MelonEvent OnPreModsLoaded = new(true); - - internal readonly static MelonEvent MelonHarmonyEarlyInit = new(true); - internal readonly static MelonEvent MelonHarmonyInit = new(true); - } + /// + /// Called after all MelonPlugins are initialized. + /// + public static readonly MelonEvent OnPreInitialization = new(true); + + /// + /// Called after Game Initialization, before OnApplicationStart and before Assembly Generation on Il2Cpp games. + /// + public static readonly MelonEvent OnApplicationEarlyStart = new(true); + + /// + /// Called after all MelonMods are initialized and before the right Support Module is loaded. + /// + public static readonly MelonEvent OnPreSupportModule = new(true); + + /// + /// Called after all MelonLoader components are fully initialized (including all MelonMods). + /// Don't use this event to initialize your Melons anymore! Instead, override . + /// + public static readonly MelonEvent OnApplicationStart = new(true); + + /// + /// Called when the first 'Start' Unity Messages are invoked. + /// + public static readonly MelonEvent OnApplicationLateStart = new(true); + + /// + /// Called before the Application is closed. It is not possible to prevent the game from closing at this point. + /// + public static readonly MelonEvent OnApplicationDefiniteQuit = new(true); + + /// + /// Called on a quit request. It is possible to abort the request in this callback. + /// + public static readonly MelonEvent OnApplicationQuit = new(); + + /// + /// Called once per frame. + /// + public static readonly MelonEvent OnUpdate = new(); + + /// + /// Called every 0.02 seconds, unless Time.fixedDeltaTime has a different Value. It is recommended to do all important Physics calculations inside this Callback. + /// + public static readonly MelonEvent OnFixedUpdate = new(); + + /// + /// Called once per frame, after . + /// + public static readonly MelonEvent OnLateUpdate = new(); + + /// + /// Called at every IMGUI event. Only use this for drawing IMGUI Elements. + /// + public static readonly MelonEvent OnGUI = new(); + + /// + /// Called when a new Scene is loaded. + /// + /// Arguments: + ///
: Build Index of the Scene.
+ ///
: Name of the Scene.
+ ///
+ ///
+ public static readonly MelonEvent OnSceneWasLoaded = new(); + + /// + /// Called once a Scene is initialized. + /// + /// Arguments: + ///
: Build Index of the Scene.
+ ///
: Name of the Scene.
+ ///
+ ///
+ public static readonly MelonEvent OnSceneWasInitialized = new(); + + /// + /// Called once a Scene unloads. + /// + /// Arguments: + ///
: Build Index of the Scene.
+ ///
: Name of the Scene.
+ ///
+ ///
+ public static readonly MelonEvent OnSceneWasUnloaded = new(); + + /// + /// Called before MelonMods are loaded from the Mods folder. + /// + public static readonly MelonEvent OnPreModsLoaded = new(true); + + internal static readonly MelonEvent MelonHarmonyEarlyInit = new(true); + internal static readonly MelonEvent MelonHarmonyInit = new(true); } diff --git a/MelonLoader/MelonGameAttribute.cs b/MelonLoader/MelonGameAttribute.cs index c1e1b8e4f..5ddca6e1a 100644 --- a/MelonLoader/MelonGameAttribute.cs +++ b/MelonLoader/MelonGameAttribute.cs @@ -1,49 +1,52 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class MelonGameAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class MelonGameAttribute : Attribute + public MelonGameAttribute(string developer = null, string name = null) { - public MelonGameAttribute(string developer = null, string name = null) { Developer = developer; Name = name; } - - /// - /// Developer of the Game. - /// - public string Developer { get; internal set; } - - /// - /// Name of the Game. - /// - public string Name { get; internal set; } - - /// - /// If the Attribute is set as Universal or not. - /// - public bool Universal { get => (string.IsNullOrEmpty(Developer) || Developer.Equals("UNKNOWN") || string.IsNullOrEmpty(Name) || Name.Equals("UNKNOWN")); } - - /// - /// Returns true or false if the Game is compatible with this Assembly. - /// - public bool IsCompatible(string developer, string gameName) => (Universal || (!string.IsNullOrEmpty(developer) && Developer.Equals(developer) && !string.IsNullOrEmpty(gameName) && Name.Equals(gameName))); - - /// - /// Returns true or false if the Game is compatible with this Assembly. - /// - public bool IsCompatible(MelonGameAttribute att) => (IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.Name.Equals(Name))); - - /// - /// Returns true or false if the Game is compatible with this Assembly specifically because of Universal Compatibility. - /// - public bool IsCompatibleBecauseUniversal(MelonGameAttribute att) => ((att == null) || Universal || att.Universal); - - [Obsolete("IsCompatible(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] - public bool IsCompatible(MelonModGameAttribute att) => ((att == null) || IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.GameName.Equals(Name))); - [Obsolete("IsCompatible(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] - public bool IsCompatible(MelonPluginGameAttribute att) => ((att == null) || IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.GameName.Equals(Name))); - [Obsolete("IsCompatibleBecauseUniversal(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] - public bool IsCompatibleBecauseUniversal(MelonModGameAttribute att) => ((att == null) || Universal || (string.IsNullOrEmpty(att.Developer) || string.IsNullOrEmpty(att.GameName))); - [Obsolete("IsCompatibleBecauseUniversal(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] - public bool IsCompatibleBecauseUniversal(MelonPluginGameAttribute att) => ((att == null) || Universal || (string.IsNullOrEmpty(att.Developer) || string.IsNullOrEmpty(att.GameName))); + Developer = developer; + Name = name; } + + /// + /// Developer of the Game. + /// + public string Developer { get; internal set; } + + /// + /// Name of the Game. + /// + public string Name { get; internal set; } + + /// + /// If the Attribute is set as Universal or not. + /// + public bool Universal { get => string.IsNullOrEmpty(Developer) || Developer.Equals("UNKNOWN") || string.IsNullOrEmpty(Name) || Name.Equals("UNKNOWN"); } + + /// + /// Returns true or false if the Game is compatible with this Assembly. + /// + public bool IsCompatible(string developer, string gameName) => Universal || (!string.IsNullOrEmpty(developer) && Developer.Equals(developer) && !string.IsNullOrEmpty(gameName) && Name.Equals(gameName)); + + /// + /// Returns true or false if the Game is compatible with this Assembly. + /// + public bool IsCompatible(MelonGameAttribute att) => IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.Name.Equals(Name)); + + /// + /// Returns true or false if the Game is compatible with this Assembly specifically because of Universal Compatibility. + /// + public bool IsCompatibleBecauseUniversal(MelonGameAttribute att) => (att == null) || Universal || att.Universal; + + [Obsolete("IsCompatible(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + public bool IsCompatible(MelonModGameAttribute att) => (att == null) || IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.GameName.Equals(Name)); + [Obsolete("IsCompatible(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + public bool IsCompatible(MelonPluginGameAttribute att) => (att == null) || IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.GameName.Equals(Name)); + [Obsolete("IsCompatibleBecauseUniversal(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + public bool IsCompatibleBecauseUniversal(MelonModGameAttribute att) => (att == null) || Universal || string.IsNullOrEmpty(att.Developer) || string.IsNullOrEmpty(att.GameName); + [Obsolete("IsCompatibleBecauseUniversal(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + public bool IsCompatibleBecauseUniversal(MelonPluginGameAttribute att) => (att == null) || Universal || string.IsNullOrEmpty(att.Developer) || string.IsNullOrEmpty(att.GameName); } \ No newline at end of file diff --git a/MelonLoader/MelonGameVersionAttribute.cs b/MelonLoader/MelonGameVersionAttribute.cs index f2e66653a..0b65e5695 100644 --- a/MelonLoader/MelonGameVersionAttribute.cs +++ b/MelonLoader/MelonGameVersionAttribute.cs @@ -1,21 +1,20 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class MelonGameVersionAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class MelonGameVersionAttribute : Attribute - { - public MelonGameVersionAttribute(string version = null) - => Version = version; + public MelonGameVersionAttribute(string version = null) + => Version = version; - /// - /// Version of the Game. - /// - public string Version { get; internal set; } + /// + /// Version of the Game. + /// + public string Version { get; internal set; } - /// - /// If the Attribute is set as Universal or not. - /// - public bool Universal { get => string.IsNullOrEmpty(Version); } - } + /// + /// If the Attribute is set as Universal or not. + /// + public bool Universal { get => string.IsNullOrEmpty(Version); } } \ No newline at end of file diff --git a/MelonLoader/MelonHandler.cs b/MelonLoader/MelonHandler.cs index 1b63173a7..c7a96bc23 100644 --- a/MelonLoader/MelonHandler.cs +++ b/MelonLoader/MelonHandler.cs @@ -1,116 +1,114 @@ -using System; +using MelonLoader.Melons; +using MelonLoader.Utils; +using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Reflection; -using MelonLoader.Melons; -using MelonLoader.Utils; -namespace MelonLoader +namespace MelonLoader; + +public static class MelonHandler { - public static class MelonHandler + /// + /// Directory of Plugins. + /// + [Obsolete("Use MelonEnvironment.PluginsDirectory instead")] + public static string PluginsDirectory => MelonEnvironment.PluginsDirectory; + + /// + /// Directory of Mods. + /// + [Obsolete("Use MelonEnvironment.ModsDirectory instead")] + public static string ModsDirectory => MelonEnvironment.ModsDirectory; + + internal static void Setup() + { + if (!Directory.Exists(MelonEnvironment.PluginsDirectory)) + Directory.CreateDirectory(MelonEnvironment.PluginsDirectory); + + if (!Directory.Exists(MelonEnvironment.ModsDirectory)) + Directory.CreateDirectory(MelonEnvironment.ModsDirectory); + } + + public static void LoadUserlibs(string path) + => MelonFolderHandler.Scan(MelonFolderHandler.eScanType.UserLibs, path); + + public static void LoadMelonsFromDirectory(string path) + where T : MelonTypeBase + => MelonFolderHandler.Scan( + (typeof(T) == typeof(MelonMod)) ? MelonFolderHandler.eScanType.Mods : MelonFolderHandler.eScanType.Plugins, + path); + + #region Obsolete Members + /// + /// List of Plugins. + /// + [Obsolete("Use 'MelonPlugin.RegisteredMelons' instead.")] + public static List Plugins => MelonTypeBase.RegisteredMelons.ToList(); + + /// + /// List of Mods. + /// + [Obsolete("Use 'MelonMod.RegisteredMelons' instead.")] + public static List Mods => MelonTypeBase.RegisteredMelons.ToList(); + + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + public static void LoadFromFile(string filelocation, bool is_plugin) => LoadFromFile(filelocation); + + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + public static void LoadFromByteArray(byte[] filedata, string filelocation) => LoadFromByteArray(filedata, filepath: filelocation); + + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + public static void LoadFromByteArray(byte[] filedata, string filelocation, bool is_plugin) => LoadFromByteArray(filedata, filepath: filelocation); + + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + public static void LoadFromAssembly(Assembly asm, string filelocation, bool is_plugin) => LoadFromAssembly(asm, filelocation); + + [Obsolete("Use 'MelonBase.Hash' instead.")] + public static string GetMelonHash(MelonBase melonBase) + => melonBase.Hash; + + [Obsolete("Use 'MelonBase.RegisteredMelons.Exists(1)' instead.")] + public static bool IsMelonAlreadyLoaded(string name) + => MelonBase._registeredMelons.Exists(x => x.Info.Name == name); + + [Obsolete("Use 'MelonPlugin.RegisteredMelons.Exists(1)' instead.")] + public static bool IsPluginAlreadyLoaded(string name) + => MelonTypeBase._registeredMelons.Exists(x => x.Info.Name == name); + + [Obsolete("Use 'MelonMod.RegisteredMelons.Exists(1)' instead.")] + public static bool IsModAlreadyLoaded(string name) + => MelonTypeBase._registeredMelons.Exists(x => x.Info.Name == name); + + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + public static void LoadFromFile(string filepath, string symbolspath = null) + { + var asm = MelonAssembly.LoadMelonAssembly(filepath); + if (asm == null) + return; + + MelonBase.RegisterSorted(asm.LoadedMelons); + } + + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + public static void LoadFromByteArray(byte[] filedata, byte[] symbolsdata = null, string filepath = null) { - /// - /// Directory of Plugins. - /// - [Obsolete("Use MelonEnvironment.PluginsDirectory instead")] - public static string PluginsDirectory => MelonEnvironment.PluginsDirectory; - - /// - /// Directory of Mods. - /// - [Obsolete("Use MelonEnvironment.ModsDirectory instead")] - public static string ModsDirectory => MelonEnvironment.ModsDirectory; - - internal static void Setup() - { - if (!Directory.Exists(MelonEnvironment.PluginsDirectory)) - Directory.CreateDirectory(MelonEnvironment.PluginsDirectory); - - if (!Directory.Exists(MelonEnvironment.ModsDirectory)) - Directory.CreateDirectory(MelonEnvironment.ModsDirectory); - } - - public static void LoadUserlibs(string path) - => MelonFolderHandler.Scan(MelonFolderHandler.eScanType.UserLibs, path); - - public static void LoadMelonsFromDirectory(string path) - where T : MelonTypeBase - => MelonFolderHandler.Scan( - ((typeof(T) == typeof(MelonMod)) ? MelonFolderHandler.eScanType.Mods : MelonFolderHandler.eScanType.Plugins), - path); - - #region Obsolete Members - /// - /// List of Plugins. - /// - [Obsolete("Use 'MelonPlugin.RegisteredMelons' instead.")] - public static List Plugins => MelonTypeBase.RegisteredMelons.ToList(); - - /// - /// List of Mods. - /// - [Obsolete("Use 'MelonMod.RegisteredMelons' instead.")] - public static List Mods => MelonTypeBase.RegisteredMelons.ToList(); - - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromFile(string filelocation, bool is_plugin) => LoadFromFile(filelocation); - - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromByteArray(byte[] filedata, string filelocation) => LoadFromByteArray(filedata, filepath: filelocation); - - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromByteArray(byte[] filedata, string filelocation, bool is_plugin) => LoadFromByteArray(filedata, filepath: filelocation); - - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromAssembly(Assembly asm, string filelocation, bool is_plugin) => LoadFromAssembly(asm, filelocation); - - [Obsolete("Use 'MelonBase.Hash' instead.")] - public static string GetMelonHash(MelonBase melonBase) - => melonBase.Hash; - - [Obsolete("Use 'MelonBase.RegisteredMelons.Exists(1)' instead.")] - public static bool IsMelonAlreadyLoaded(string name) - => MelonBase._registeredMelons.Exists(x => x.Info.Name == name); - - [Obsolete("Use 'MelonPlugin.RegisteredMelons.Exists(1)' instead.")] - public static bool IsPluginAlreadyLoaded(string name) - => MelonTypeBase._registeredMelons.Exists(x => x.Info.Name == name); - - [Obsolete("Use 'MelonMod.RegisteredMelons.Exists(1)' instead.")] - public static bool IsModAlreadyLoaded(string name) - => MelonTypeBase._registeredMelons.Exists(x => x.Info.Name == name); - - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromFile(string filepath, string symbolspath = null) - { - var asm = MelonAssembly.LoadMelonAssembly(filepath); - if (asm == null) - return; - - MelonBase.RegisterSorted(asm.LoadedMelons); - } - - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromByteArray(byte[] filedata, byte[] symbolsdata = null, string filepath = null) - { - var asm = MelonAssembly.LoadRawMelonAssembly(filepath, filedata, symbolsdata); - if (asm == null) - return; - - MelonBase.RegisterSorted(asm.LoadedMelons); - } - - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromAssembly(Assembly asm, string filepath = null) - { - var ma = MelonAssembly.LoadMelonAssembly(filepath, asm); - if (ma == null) - return; - - MelonBase.RegisterSorted(ma.LoadedMelons); - } - #endregion + var asm = MelonAssembly.LoadRawMelonAssembly(filepath, filedata, symbolsdata); + if (asm == null) + return; + + MelonBase.RegisterSorted(asm.LoadedMelons); + } + + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + public static void LoadFromAssembly(Assembly asm, string filepath = null) + { + var ma = MelonAssembly.LoadMelonAssembly(filepath, asm); + if (ma == null) + return; + + MelonBase.RegisterSorted(ma.LoadedMelons); } + #endregion } diff --git a/MelonLoader/MelonIDAttribute.cs b/MelonLoader/MelonIDAttribute.cs index 5fc5656d5..f149cd2f2 100644 --- a/MelonLoader/MelonIDAttribute.cs +++ b/MelonLoader/MelonIDAttribute.cs @@ -1,16 +1,15 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonIDAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonIDAttribute : Attribute - { - /// ID of the Melon. - public string ID { get; internal set; } + /// ID of the Melon. + public string ID { get; internal set; } - public MelonIDAttribute(string id) - => ID = id; - public MelonIDAttribute(int id) - => ID = id.ToString(); - } + public MelonIDAttribute(string id) + => ID = id; + public MelonIDAttribute(int id) + => ID = id.ToString(); } \ No newline at end of file diff --git a/MelonLoader/MelonIncompatibleAssembliesAttribute.cs b/MelonLoader/MelonIncompatibleAssembliesAttribute.cs index 2a7ccedb2..0ac194ea4 100644 --- a/MelonLoader/MelonIncompatibleAssembliesAttribute.cs +++ b/MelonLoader/MelonIncompatibleAssembliesAttribute.cs @@ -1,15 +1,14 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonIncompatibleAssembliesAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonIncompatibleAssembliesAttribute : Attribute - { - /// - /// The (simple) assembly names of the mods that are incompatible. - /// - public string[] AssemblyNames { get; internal set; } + /// + /// The (simple) assembly names of the mods that are incompatible. + /// + public string[] AssemblyNames { get; internal set; } - public MelonIncompatibleAssembliesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } - } + public MelonIncompatibleAssembliesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } } \ No newline at end of file diff --git a/MelonLoader/MelonInfoAttribute.cs b/MelonLoader/MelonInfoAttribute.cs index 9eb0a1e11..f5c1df90e 100644 --- a/MelonLoader/MelonInfoAttribute.cs +++ b/MelonLoader/MelonInfoAttribute.cs @@ -1,93 +1,86 @@ using Semver; using System; -namespace MelonLoader -{ - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class MelonInfoAttribute : Attribute - { - /// - /// System.Type of the Melon. - /// - public Type SystemType { get; internal set; } +namespace MelonLoader; - /// - /// Name of the Melon. - /// - public string Name { get; internal set; } +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class MelonInfoAttribute : Attribute +{ + /// + /// System.Type of the Melon. + /// + public Type SystemType { get; internal set; } - /// - /// Version of the Melon. - /// - public string Version { get; internal set; } + /// + /// Name of the Melon. + /// + public string Name { get; internal set; } - /// - /// Semantic Version of the Melon. Will be null if Version is not using the Semantic Versioning format. - /// - public SemVersion SemanticVersion { get; internal set; } + /// + /// Version of the Melon. + /// + public string Version { get; internal set; } - /// - /// Author of the Melon. - /// - public string Author { get; internal set; } // This used to be optional, but is now required + /// + /// Semantic Version of the Melon. Will be null if Version is not using the Semantic Versioning format. + /// + public SemVersion SemanticVersion { get; internal set; } - /// - /// Download Link of the Melon. - /// - public string DownloadLink { get; internal set; } // Might get Removed. Not sure yet. + /// + /// Author of the Melon. + /// + public string Author { get; internal set; } // This used to be optional, but is now required - /// - /// MelonInfo constructor. - /// - /// The main Melon type of the Melon (for example TestMod) - /// Name of the Melon - /// Version of the Melon - /// Author of the Melon - /// URL to the download link of the mod [optional] - public MelonInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) - { - if (type == null) - throw new ArgumentNullException(nameof(type)); + /// + /// Download Link of the Melon. + /// + public string DownloadLink { get; internal set; } // Might get Removed. Not sure yet. - SystemType = type; - Name = name ?? "UNKNOWN"; - Author = author ?? "UNKNOWN"; - DownloadLink = downloadLink; // Might get Removed. Not sure yet. + /// + /// MelonInfo constructor. + /// + /// The main Melon type of the Melon (for example TestMod) + /// Name of the Melon + /// Version of the Melon + /// Author of the Melon + /// URL to the download link of the mod [optional] + public MelonInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) + { + SystemType = type ?? throw new ArgumentNullException(nameof(type)); + Name = name ?? "UNKNOWN"; + Author = author ?? "UNKNOWN"; + DownloadLink = downloadLink; // Might get Removed. Not sure yet. - if (string.IsNullOrEmpty(version)) - Version = "1.0.0"; - else - Version = version; + Version = string.IsNullOrEmpty(version) ? "1.0.0" : version; - if (SemVersion.TryParse(Version, out SemVersion semver)) - SemanticVersion = semver; - } + if (SemVersion.TryParse(Version, out var semver)) + SemanticVersion = semver; + } - /// - /// MelonInfo constructor. - /// - /// The main Melon type of the Melon (for example TestMod) - /// Name of the Melon - /// Version Major of the Melon (Using the Semantic Versioning format) - /// Version Minor of the Melon (Using the Semantic Versioning format) - /// Version Revision of the Melon (Using the Semantic Versioning format) - /// Version Identifier of the Melon (Using the Semantic Versioning format) - /// Author of the Melon - /// URL to the download link of the mod [optional] - public MelonInfoAttribute(Type type, string name, int versionMajor, int versionMinor, int versionRevision, string versionIdentifier, string author, string downloadLink = null) - : this(type, name, $"{versionMajor}.{versionMinor}.{versionRevision}{(string.IsNullOrEmpty(versionIdentifier) ? "" : versionIdentifier)}", author, downloadLink) { } + /// + /// MelonInfo constructor. + /// + /// The main Melon type of the Melon (for example TestMod) + /// Name of the Melon + /// Version Major of the Melon (Using the Semantic Versioning format) + /// Version Minor of the Melon (Using the Semantic Versioning format) + /// Version Revision of the Melon (Using the Semantic Versioning format) + /// Version Identifier of the Melon (Using the Semantic Versioning format) + /// Author of the Melon + /// URL to the download link of the mod [optional] + public MelonInfoAttribute(Type type, string name, int versionMajor, int versionMinor, int versionRevision, string versionIdentifier, string author, string downloadLink = null) + : this(type, name, $"{versionMajor}.{versionMinor}.{versionRevision}{(string.IsNullOrEmpty(versionIdentifier) ? "" : versionIdentifier)}", author, downloadLink) { } - /// - /// MelonInfo constructor. - /// - /// The main Melon type of the Melon (for example TestMod) - /// Name of the Melon - /// Version Major of the Melon (Using the Semantic Versioning format) - /// Version Minor of the Melon (Using the Semantic Versioning format) - /// Version Revision of the Melon (Using the Semantic Versioning format) - /// Author of the Melon - /// URL to the download link of the mod [optional] - public MelonInfoAttribute(Type type, string name, int versionMajor, int versionMinor, int versionRevision, string author, string downloadLink = null) - : this(type, name, versionMajor, versionMinor, versionRevision, null, author, downloadLink) { } - } + /// + /// MelonInfo constructor. + /// + /// The main Melon type of the Melon (for example TestMod) + /// Name of the Melon + /// Version Major of the Melon (Using the Semantic Versioning format) + /// Version Minor of the Melon (Using the Semantic Versioning format) + /// Version Revision of the Melon (Using the Semantic Versioning format) + /// Author of the Melon + /// URL to the download link of the mod [optional] + public MelonInfoAttribute(Type type, string name, int versionMajor, int versionMinor, int versionRevision, string author, string downloadLink = null) + : this(type, name, versionMajor, versionMinor, versionRevision, null, author, downloadLink) { } } \ No newline at end of file diff --git a/MelonLoader/MelonLaunchOptions.cs b/MelonLoader/MelonLaunchOptions.cs index 92be5cfc3..56c87b948 100644 --- a/MelonLoader/MelonLaunchOptions.cs +++ b/MelonLoader/MelonLaunchOptions.cs @@ -1,206 +1,204 @@ using System; using System.Collections.Generic; -namespace MelonLoader +namespace MelonLoader; + +public static class MelonLaunchOptions { - public static class MelonLaunchOptions + private static readonly Dictionary WithoutArg = []; + private static readonly Dictionary> WithArg = []; + private static string[] _cmd; + + /// + /// Dictionary of all Arguments with value (if found) that were not used by MelonLoader + /// + /// Key is the argument, Value is the value for the argument, null if not found + /// + /// + public static Dictionary ExternalArguments { get; private set; } = []; + public static Dictionary InternalArguments { get; private set; } = []; + + /// + /// Array of All Command Line Arguments + /// + public static string[] CommandLineArgs { - private static Dictionary WithoutArg = new Dictionary(); - private static Dictionary> WithArg = new Dictionary>(); - private static string[] _cmd; - - /// - /// Dictionary of all Arguments with value (if found) that were not used by MelonLoader - /// - /// Key is the argument, Value is the value for the argument, null if not found - /// - /// - public static Dictionary ExternalArguments { get; private set; } = new Dictionary(); - public static Dictionary InternalArguments { get; private set; } = new Dictionary(); - - /// - /// Array of All Command Line Arguments - /// - public static string[] CommandLineArgs + get { - get - { - if (_cmd == null) - _cmd = Environment.GetCommandLineArgs(); - return _cmd; - } + _cmd ??= Environment.GetCommandLineArgs(); + return _cmd; } + } - internal static void Load() + internal static void Load() + { + var args = CommandLineArgs; + var maxLen = args.Length; + for (var i = 1; i < maxLen; i++) { - string[] args = CommandLineArgs; - int maxLen = args.Length; - for (int i = 1; i < maxLen; i++) + var fullcmd = args[i]; + if (string.IsNullOrEmpty(fullcmd)) + continue; + + // Parse Prefix + var noPrefixCmd = fullcmd; + if (noPrefixCmd.StartsWith("--")) + noPrefixCmd = noPrefixCmd.Remove(0, 2); + else if (noPrefixCmd.StartsWith("-")) + noPrefixCmd = noPrefixCmd.Remove(0, 1); + else { - string fullcmd = args[i]; - if (string.IsNullOrEmpty(fullcmd)) - continue; - - // Parse Prefix - string noPrefixCmd = fullcmd; - if (noPrefixCmd.StartsWith("--")) - noPrefixCmd = noPrefixCmd.Remove(0, 2); - else if (noPrefixCmd.StartsWith("-")) - noPrefixCmd = noPrefixCmd.Remove(0, 1); - else - { - // Unknown Command, Add it to Dictionary - ExternalArguments.Add(noPrefixCmd, null); - continue; - } - - // Parse Argumentless Commands - if (WithoutArg.TryGetValue(noPrefixCmd, out Action withoutArgFunc)) - { - InternalArguments.Add(noPrefixCmd, null); - withoutArgFunc(); - continue; - } - - // Parse Argument - string cmdArg = null; - if (noPrefixCmd.Contains("=")) - { - string[] split = noPrefixCmd.Split('='); - noPrefixCmd = split[0]; - cmdArg = split[1]; - } - - if ((string.IsNullOrEmpty(cmdArg) - && ((i + 1) >= maxLen)) - || string.IsNullOrEmpty(cmdArg) - || cmdArg.StartsWith("--") - || cmdArg.StartsWith("-")) - { - // Unknown Command, Add it to Dictionary - ExternalArguments.Add(noPrefixCmd, null); - continue; - } - - // Parse Argument Commands - if (WithArg.TryGetValue(noPrefixCmd, out Action withArgFunc)) - { - InternalArguments.Add(noPrefixCmd, cmdArg); - withArgFunc(cmdArg); - continue; - } - - // Unknown Command with Argument, Add it to Dictionary - ExternalArguments.Add(noPrefixCmd, cmdArg); + // Unknown Command, Add it to Dictionary + ExternalArguments.Add(noPrefixCmd, null); + continue; } - } - #region Obsolete - - [Obsolete("Use LoaderConfig.Current.Loader instead.")] - public static class Core - { - [Obsolete("This option isn't used anymore.")] - public enum LoadModeEnum + // Parse Argumentless Commands + if (WithoutArg.TryGetValue(noPrefixCmd, out var withoutArgFunc)) { - NORMAL, - DEV, - BOTH + InternalArguments.Add(noPrefixCmd, null); + withoutArgFunc(); + continue; } - [Obsolete("This option isn't used anymore. It will always return NORMAL.")] - public static LoadModeEnum LoadMode_Plugins => LoadModeEnum.NORMAL; - - [Obsolete("This option isn't used anymore. It will always return NORMAL.")] - public static LoadModeEnum LoadMode_Mods => LoadModeEnum.NORMAL; + // Parse Argument + string cmdArg = null; + if (noPrefixCmd.Contains("=")) + { + var split = noPrefixCmd.Split('='); + noPrefixCmd = split[0]; + cmdArg = split[1]; + } - [Obsolete("Use LoaderConfig.Current.Loader.ForceQuit instead.")] - public static bool QuitFix => LoaderConfig.Current.Loader.ForceQuit; + if ((string.IsNullOrEmpty(cmdArg) + && ((i + 1) >= maxLen)) + || string.IsNullOrEmpty(cmdArg) + || cmdArg.StartsWith("--") + || cmdArg.StartsWith("-")) + { + // Unknown Command, Add it to Dictionary + ExternalArguments.Add(noPrefixCmd, null); + continue; + } - [Obsolete("Use LoaderConfig.Current.Loader.DisableStartScreen instead.")] - public static bool StartScreen => !LoaderConfig.Current.Loader.DisableStartScreen; + // Parse Argument Commands + if (WithArg.TryGetValue(noPrefixCmd, out var withArgFunc)) + { + InternalArguments.Add(noPrefixCmd, cmdArg); + withArgFunc(cmdArg); + continue; + } - [Obsolete("Use LoaderConfig.Current.UnityEngine.VersionOverride instead.")] - public static string UnityVersion => LoaderConfig.Current.UnityEngine.VersionOverride; + // Unknown Command with Argument, Add it to Dictionary + ExternalArguments.Add(noPrefixCmd, cmdArg); + } + } - [Obsolete("Use LoaderConfig.Current.Loader.DebugMode instead.")] - public static bool IsDebug => LoaderConfig.Current.Loader.DebugMode; + #region Obsolete - [Obsolete("Use LoaderConfig.Current.Loader.LaunchDebugger instead.")] - public static bool UserWantsDebugger => LoaderConfig.Current.Loader.LaunchDebugger; + [Obsolete("Use LoaderConfig.Current.Loader instead.")] + public static class Core + { + [Obsolete("This option isn't used anymore.")] + public enum LoadModeEnum + { + NORMAL, + DEV, + BOTH } - [Obsolete("Use LoaderConfig.Current.Console instead.")] - public static class Console - { - [Obsolete("Use LoaderConfig.CoreConfig.LoaderTheme instead.")] - public enum DisplayMode - { - NORMAL, - MAGENTA, - RAINBOW, - RANDOMRAINBOW, - LEMON - }; + [Obsolete("This option isn't used anymore. It will always return NORMAL.")] + public static LoadModeEnum LoadMode_Plugins => LoadModeEnum.NORMAL; - [Obsolete("Use LoaderConfig.Current.Loader.Theme instead.")] - public static DisplayMode Mode => (DisplayMode)LoaderConfig.Current.Loader.Theme; + [Obsolete("This option isn't used anymore. It will always return NORMAL.")] + public static LoadModeEnum LoadMode_Mods => LoadModeEnum.NORMAL; - [Obsolete("Use LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner instead.")] - public static bool CleanUnityLogs => !LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner; + [Obsolete("Use LoaderConfig.Current.Loader.ForceQuit instead.")] + public static bool QuitFix => LoaderConfig.Current.Loader.ForceQuit; - [Obsolete("Use LoaderConfig.Current.Console.DontSetTitle instead.")] - public static bool ShouldSetTitle => !LoaderConfig.Current.Console.DontSetTitle; + [Obsolete("Use LoaderConfig.Current.Loader.DisableStartScreen instead.")] + public static bool StartScreen => !LoaderConfig.Current.Loader.DisableStartScreen; - [Obsolete("Use LoaderConfig.Current.Console.AlwaysOnTop instead.")] - public static bool AlwaysOnTop => LoaderConfig.Current.Console.AlwaysOnTop; + [Obsolete("Use LoaderConfig.Current.UnityEngine.VersionOverride instead.")] + public static string UnityVersion => LoaderConfig.Current.UnityEngine.VersionOverride; - [Obsolete("Use LoaderConfig.Current.Console.Hide instead.")] - public static bool ShouldHide => LoaderConfig.Current.Console.Hide; + [Obsolete("Use LoaderConfig.Current.Loader.DebugMode instead.")] + public static bool IsDebug => LoaderConfig.Current.Loader.DebugMode; - [Obsolete("Use LoaderConfig.Current.Console.HideWarnings instead.")] - public static bool HideWarnings => LoaderConfig.Current.Console.HideWarnings; - } + [Obsolete("Use LoaderConfig.Current.Loader.LaunchDebugger instead.")] + public static bool UserWantsDebugger => LoaderConfig.Current.Loader.LaunchDebugger; + } - [Obsolete("Use LoaderConfig.Current.UnityEngine instead.")] - public static class Cpp2IL + [Obsolete("Use LoaderConfig.Current.Console instead.")] + public static class Console + { + [Obsolete("Use LoaderConfig.CoreConfig.LoaderTheme instead.")] + public enum DisplayMode { - [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer instead.")] - public static bool CallAnalyzer => LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer; + NORMAL, + MAGENTA, + RAINBOW, + RANDOMRAINBOW, + LEMON + }; - [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector instead.")] - public static bool NativeMethodDetector => LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector; - } + [Obsolete("Use LoaderConfig.Current.Loader.Theme instead.")] + public static DisplayMode Mode => (DisplayMode)LoaderConfig.Current.Loader.Theme; - [Obsolete("Use LoaderConfig.Current.UnityEngine instead.")] - public static class Il2CppAssemblyGenerator - { - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceRegeneration instead.")] - public static bool ForceRegeneration => LoaderConfig.Current.UnityEngine.ForceRegeneration; + [Obsolete("Use LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner instead.")] + public static bool CleanUnityLogs => !LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner; - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceOfflineGeneration instead.")] - public static bool OfflineMode => LoaderConfig.Current.UnityEngine.ForceOfflineGeneration; + [Obsolete("Use LoaderConfig.Current.Console.DontSetTitle instead.")] + public static bool ShouldSetTitle => !LoaderConfig.Current.Console.DontSetTitle; - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion instead.")] - public static string ForceVersion_Dumper => LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; + [Obsolete("Use LoaderConfig.Current.Console.AlwaysOnTop instead.")] + public static bool AlwaysOnTop => LoaderConfig.Current.Console.AlwaysOnTop; - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceGeneratorRegex instead.")] - public static string ForceRegex => LoaderConfig.Current.UnityEngine.ForceGeneratorRegex; - } + [Obsolete("Use LoaderConfig.Current.Console.Hide instead.")] + public static bool ShouldHide => LoaderConfig.Current.Console.Hide; - [Obsolete("Use LoaderConfig.Logs instead.")] - public static class Logger - { - [Obsolete("Use LoaderConfig.Current.Logs.MaxLogs instead.")] - public static int MaxLogs => (int)LoaderConfig.Current.Logs.MaxLogs; + [Obsolete("Use LoaderConfig.Current.Console.HideWarnings instead.")] + public static bool HideWarnings => LoaderConfig.Current.Console.HideWarnings; + } - [Obsolete("This option isn't used anymore. It will always return 10.")] - public static int MaxWarnings => 10; + [Obsolete("Use LoaderConfig.Current.UnityEngine instead.")] + public static class Cpp2IL + { + [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer instead.")] + public static bool CallAnalyzer => LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer; - [Obsolete("This option isn't used anymore. It will always return 10.")] - public static int MaxErrors => 10; - } + [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector instead.")] + public static bool NativeMethodDetector => LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector; + } - #endregion Obsolete + [Obsolete("Use LoaderConfig.Current.UnityEngine instead.")] + public static class Il2CppAssemblyGenerator + { + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceRegeneration instead.")] + public static bool ForceRegeneration => LoaderConfig.Current.UnityEngine.ForceRegeneration; + + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceOfflineGeneration instead.")] + public static bool OfflineMode => LoaderConfig.Current.UnityEngine.ForceOfflineGeneration; + + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion instead.")] + public static string ForceVersion_Dumper => LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; + + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceGeneratorRegex instead.")] + public static string ForceRegex => LoaderConfig.Current.UnityEngine.ForceGeneratorRegex; + } + + [Obsolete("Use LoaderConfig.Logs instead.")] + public static class Logger + { + [Obsolete("Use LoaderConfig.Current.Logs.MaxLogs instead.")] + public static int MaxLogs => (int)LoaderConfig.Current.Logs.MaxLogs; + + [Obsolete("This option isn't used anymore. It will always return 10.")] + public static int MaxWarnings => 10; + + [Obsolete("This option isn't used anymore. It will always return 10.")] + public static int MaxErrors => 10; } + + #endregion Obsolete } diff --git a/MelonLoader/MelonLogger.cs b/MelonLoader/MelonLogger.cs index 946b21b29..ac81854dc 100644 --- a/MelonLoader/MelonLogger.cs +++ b/MelonLoader/MelonLogger.cs @@ -1,351 +1,350 @@ +using MelonLoader.Bootstrap.Logging; +using MelonLoader.InternalUtils; using MelonLoader.Utils; using System; using System.Drawing; -using static MelonLoader.Utils.LoggerUtils; using System.Text.RegularExpressions; -using MelonLoader.Bootstrap.Logging; -using MelonLoader.InternalUtils; +using static MelonLoader.Utils.LoggerUtils; + +namespace MelonLoader; -namespace MelonLoader +public class MelonLogger { - public class MelonLogger - { - public static readonly Color DefaultMelonColor = Color.Cyan; - public static readonly Color DefaultTextColor = Color.LightGray; + public static readonly Color DefaultMelonColor = Color.Cyan; + public static readonly Color DefaultTextColor = Color.LightGray; - //Identical to Msg(string) except it skips walking the stack to find a melon - internal static void MsgDirect(string txt) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, txt, true); + //Identical to Msg(string) except it skips walking the stack to find a melon + internal static void MsgDirect(string txt) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, txt, true); - public static void Msg(object obj) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, obj.ToString()); + public static void Msg(object obj) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, obj.ToString()); - public static void Msg(string txt) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, txt); + public static void Msg(string txt) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, txt); - public static void Msg(string txt, params object[] args) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); + public static void Msg(string txt, params object[] args) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); - public static void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); + public static void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); - public static void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); + public static void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); - public static void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); + public static void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); - //Identical to Msg(Color, string) except it skips walking the stack to find a melon - public static void MsgDirect(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt, true); + //Identical to Msg(Color, string) except it skips walking the stack to find a melon + public static void MsgDirect(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt, true); - public static void Msg(Color txt_color, object obj) => NativeMsg(DefaultMelonColor, txt_color, null, obj.ToString()); + public static void Msg(Color txt_color, object obj) => NativeMsg(DefaultMelonColor, txt_color, null, obj.ToString()); - public static void Msg(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt); + public static void Msg(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt); - public static void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); + public static void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); - //Identical to MsgPastel(string) except it skips walking the stack to find a melon - internal static void MsgPastelDirect(string txt) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, txt, true); + //Identical to MsgPastel(string) except it skips walking the stack to find a melon + internal static void MsgPastelDirect(string txt) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, txt, true); - public static void MsgPastel(object obj) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, obj.ToString()); + public static void MsgPastel(object obj) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, obj.ToString()); - public static void MsgPastel(string txt) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, txt); + public static void MsgPastel(string txt) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, txt); - public static void MsgPastel(string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); + public static void MsgPastel(string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); - public static void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); + public static void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); - public static void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); + public static void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); - public static void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); + public static void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); - //Identical to MsgPastel(Color, string) except it skips walking the stack to find a melon - public static void MsgPastelDirect(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt, true); + //Identical to MsgPastel(Color, string) except it skips walking the stack to find a melon + public static void MsgPastelDirect(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt, true); - public static void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DefaultMelonColor, txt_color, null, obj.ToString()); + public static void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DefaultMelonColor, txt_color, null, obj.ToString()); - public static void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt); + public static void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt); - public static void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); + public static void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); - public static void Warning(object obj) => NativeWarning(null, obj.ToString()); + public static void Warning(object obj) => NativeWarning(null, obj.ToString()); - public static void Warning(string txt) => NativeWarning(null, txt); + public static void Warning(string txt) => NativeWarning(null, txt); - public static void Warning(string txt, params object[] args) => NativeWarning(null, string.Format(txt, args)); + public static void Warning(string txt, params object[] args) => NativeWarning(null, string.Format(txt, args)); - public static void Error(object obj) => NativeError(null, obj.ToString()); + public static void Error(object obj) => NativeError(null, obj.ToString()); - public static void Error(string txt) => NativeError(null, txt); + public static void Error(string txt) => NativeError(null, txt); - public static void Error(string txt, params object[] args) => NativeError(null, string.Format(txt, args)); + public static void Error(string txt, params object[] args) => NativeError(null, string.Format(txt, args)); - public static void Error(string txt, Exception ex) => NativeError(null, $"{txt}\n{ex}"); + public static void Error(string txt, Exception ex) => NativeError(null, $"{txt}\n{ex}"); - public static void WriteLine(int length = 30) => MsgDirect(new string('-', length)); + public static void WriteLine(int length = 30) => MsgDirect(new string('-', length)); - public static void WriteLine(Color color, int length = 30) => MsgDirect(color, new string('-', length)); + public static void WriteLine(Color color, int length = 30) => MsgDirect(color, new string('-', length)); - private static void NativeMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) + private static void NativeMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) + { + if (namesection == null) { - if (namesection == null) + var melon = MelonUtils.GetMelonFromStackTrace(); + if (melon != null && melon.Info?.Name != null) { - var melon = MelonUtils.GetMelonFromStackTrace(); - if (melon != null && melon.Info?.Name != null) - { - namesection = melon.Info.Name; - namesection_color = melon.ConsoleColor; - } + namesection = melon.Info.Name; + namesection_color = melon.ConsoleColor; } - - PassLogMsg(txt_color, txt ?? "null", namesection_color, namesection); - RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); } - private static void NativePastelMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) + PassLogMsg(txt_color, txt ?? "null", namesection_color, namesection); + RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); + } + + private static void NativePastelMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) + { + if (namesection == null) { - if (namesection == null) + var melon = MelonUtils.GetMelonFromStackTrace(); + if (melon != null && melon.Info != null) { - var melon = MelonUtils.GetMelonFromStackTrace(); - if (melon != null && melon.Info != null) - { - namesection = melon.Info.Name; - namesection_color = melon.ConsoleColor; - } + namesection = melon.Info.Name; + namesection_color = melon.ConsoleColor; } - - PastelMsg(namesection_color, txt_color, namesection, txt ?? "null"); - RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); } - private static void NativeWarning(string namesection, string txt) - { - namesection ??= MelonUtils.GetMelonFromStackTrace()?.Info?.Name; + PastelMsg(namesection_color, txt_color, namesection, txt ?? "null"); + RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); + } - Warning(namesection, txt ?? "null"); - RunWarningCallbacks(namesection, txt ?? "null"); - } + private static void NativeWarning(string namesection, string txt) + { + namesection ??= MelonUtils.GetMelonFromStackTrace()?.Info?.Name; - private static void NativeError(string namesection, string txt) - { - namesection ??= MelonUtils.GetMelonFromStackTrace()?.Info?.Name; + Warning(namesection, txt ?? "null"); + RunWarningCallbacks(namesection, txt ?? "null"); + } - PassLogError(txt ?? "null", namesection, false); - RunErrorCallbacks(namesection, txt ?? "null"); - } + private static void NativeError(string namesection, string txt) + { + namesection ??= MelonUtils.GetMelonFromStackTrace()?.Info?.Name; - public static void BigError(string namesection, string txt) - { - RunErrorCallbacks(namesection, txt ?? "null"); + PassLogError(txt ?? "null", namesection, false); + RunErrorCallbacks(namesection, txt ?? "null"); + } - PassLogError(new string('=', 50), namesection, false); - foreach (var line in txt.Split('\n')) - PassLogError(line, namesection, false); + public static void BigError(string namesection, string txt) + { + RunErrorCallbacks(namesection, txt ?? "null"); - PassLogError(new string('=', 50), namesection, false); - } + PassLogError(new string('=', 50), namesection, false); + foreach (var line in txt.Split('\n')) + PassLogError(line, namesection, false); - internal static void RunMsgCallbacks(Color namesection_color, Color txt_color, string namesection, string txt) - { - MsgCallbackHandler?.Invoke(DrawingColorToConsoleColor(namesection_color), DrawingColorToConsoleColor(txt_color), namesection, txt); - MsgDrawingCallbackHandler?.Invoke(namesection_color, txt_color, namesection, txt); - } + PassLogError(new string('=', 50), namesection, false); + } - [Obsolete("MsgCallbackHandler is obsolete. Please use MsgDrawingCallbackHandler for full Color support.")] - public static event Action MsgCallbackHandler; + internal static void RunMsgCallbacks(Color namesection_color, Color txt_color, string namesection, string txt) + { + MsgCallbackHandler?.Invoke(DrawingColorToConsoleColor(namesection_color), DrawingColorToConsoleColor(txt_color), namesection, txt); + MsgDrawingCallbackHandler?.Invoke(namesection_color, txt_color, namesection, txt); + } - public static event Action MsgDrawingCallbackHandler; + [Obsolete("MsgCallbackHandler is obsolete. Please use MsgDrawingCallbackHandler for full Color support.")] + public static event Action MsgCallbackHandler; - internal static void RunWarningCallbacks(string namesection, string txt) => WarningCallbackHandler?.Invoke(namesection, txt); + public static event Action MsgDrawingCallbackHandler; - public static event Action WarningCallbackHandler; + internal static void RunWarningCallbacks(string namesection, string txt) => WarningCallbackHandler?.Invoke(namesection, txt); - internal static void RunErrorCallbacks(string namesection, string txt) => ErrorCallbackHandler?.Invoke(namesection, txt); + public static event Action WarningCallbackHandler; - public static event Action ErrorCallbackHandler; + internal static void RunErrorCallbacks(string namesection, string txt) => ErrorCallbackHandler?.Invoke(namesection, txt); - public class Instance + public static event Action ErrorCallbackHandler; + + public class Instance + { + private readonly string Name = null; + + [Obsolete("Color is obsolete. Please use DrawingColor for full Color support.")] + private ConsoleColor Color { - private string Name = null; + get => DrawingColorToConsoleColor(DrawingColor); + set => DrawingColor = ConsoleColorToDrawingColor(value); + } - [Obsolete("Color is obsolete. Please use DrawingColor for full Color support.")] - private ConsoleColor Color - { - get => DrawingColorToConsoleColor(DrawingColor); - set => DrawingColor = ConsoleColorToDrawingColor(value); - } + private Color DrawingColor = DefaultMelonColor; - private Color DrawingColor = DefaultMelonColor; + public Instance(string name) => Name = name?.Replace(" ", "_"); - public Instance(string name) => Name = name?.Replace(" ", "_"); + [Obsolete("ConsoleColor is obsolete, use the (string, Color) constructor instead.")] + public Instance(string name, ConsoleColor color) : this(name) => Color = color; - [Obsolete("ConsoleColor is obsolete, use the (string, Color) constructor instead.")] - public Instance(string name, ConsoleColor color) : this(name) => Color = color; + public Instance(string name, Color color) : this(name) => DrawingColor = color; - public Instance(string name, Color color) : this(name) => DrawingColor = color; + public void Msg(object obj) => NativeMsg(DrawingColor, DefaultTextColor, Name, obj.ToString()); - public void Msg(object obj) => NativeMsg(DrawingColor, DefaultTextColor, Name, obj.ToString()); + public void Msg(string txt) => NativeMsg(DrawingColor, DefaultTextColor, Name, txt); - public void Msg(string txt) => NativeMsg(DrawingColor, DefaultTextColor, Name, txt); + public void Msg(string txt, params object[] args) => NativeMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); - public void Msg(string txt, params object[] args) => NativeMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); + public void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); - public void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); + public void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); - public void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); + public void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); - public void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); + public void Msg(Color txt_color, object obj) => NativeMsg(DrawingColor, txt_color, Name, obj.ToString()); - public void Msg(Color txt_color, object obj) => NativeMsg(DrawingColor, txt_color, Name, obj.ToString()); + public void Msg(Color txt_color, string txt) => NativeMsg(DrawingColor, txt_color, Name, txt); - public void Msg(Color txt_color, string txt) => NativeMsg(DrawingColor, txt_color, Name, txt); + public void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); - public void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); + public void MsgPastel(object obj) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, obj.ToString()); - public void MsgPastel(object obj) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, obj.ToString()); + public void MsgPastel(string txt) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, txt); - public void MsgPastel(string txt) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, txt); + public void MsgPastel(string txt, params object[] args) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); - public void MsgPastel(string txt, params object[] args) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); + public void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); - public void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); + public void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); - public void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); + public void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); - public void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); + public void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DrawingColor, txt_color, Name, obj.ToString()); - public void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DrawingColor, txt_color, Name, obj.ToString()); + public void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DrawingColor, txt_color, Name, txt); - public void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DrawingColor, txt_color, Name, txt); + public void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); - public void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); + public void Warning(object obj) => NativeWarning(Name, obj.ToString()); - public void Warning(object obj) => NativeWarning(Name, obj.ToString()); + public void Warning(string txt) => NativeWarning(Name, txt); - public void Warning(string txt) => NativeWarning(Name, txt); + public void Warning(string txt, params object[] args) => NativeWarning(Name, string.Format(txt, args)); - public void Warning(string txt, params object[] args) => NativeWarning(Name, string.Format(txt, args)); + public void Error(object obj) => NativeError(Name, obj.ToString()); - public void Error(object obj) => NativeError(Name, obj.ToString()); + public void Error(string txt) => NativeError(Name, txt); - public void Error(string txt) => NativeError(Name, txt); + public void Error(string txt, params object[] args) => NativeError(Name, string.Format(txt, args)); - public void Error(string txt, params object[] args) => NativeError(Name, string.Format(txt, args)); + public void Error(string txt, Exception ex) => NativeError(Name, $"{txt}\n{ex}"); - public void Error(string txt, Exception ex) => NativeError(Name, $"{txt}\n{ex}"); + public void WriteSpacer() => MelonLogger.WriteSpacer(); - public void WriteSpacer() => MelonLogger.WriteSpacer(); + public void WriteLine(int length = 30) => MelonLogger.WriteLine(length); - public void WriteLine(int length = 30) => MelonLogger.WriteLine(length); + public void WriteLine(Color color, int length = 30) => MelonLogger.WriteLine(color, length); - public void WriteLine(Color color, int length = 30) => MelonLogger.WriteLine(color, length); + public void BigError(string txt) => MelonLogger.BigError(Name, txt); + } - public void BigError(string txt) => MelonLogger.BigError(Name, txt); - } + internal static void PastelMsg(Color namesection_color, Color txt_color, string namesection, string txt) + { + // Regex to check for ANSI + var cleanTxt = Regex.Replace(txt, @"(\x1B|\e|\033)\[(.*?)m", ""); - internal static void PastelMsg(Color namesection_color, Color txt_color, string namesection, string txt) - { - // Regex to check for ANSI - var cleanTxt = Regex.Replace(txt, @"(\x1B|\e|\033)\[(.*?)m", ""); + PassLogMsg(txt_color, cleanTxt, namesection_color, namesection); + } - PassLogMsg(txt_color, cleanTxt, namesection_color, namesection); - } + internal static void Warning(string namesection, string txt) + { + PassLogError(txt, namesection, true); + } - internal static void Warning(string namesection, string txt) - { - PassLogError(txt, namesection, true); - } + internal static void ThrowInternalFailure(string txt) => Assertion.ThrowInternalFailure(txt); - internal static void ThrowInternalFailure(string txt) => Assertion.ThrowInternalFailure(txt); + internal static unsafe void WriteSpacer() + { + BootstrapInterop.Library.LogMsg(null, null, 0, null, null, 0); + } - internal static unsafe void WriteSpacer() - { - BootstrapInterop.Library.LogMsg(null, null, 0, null, null, 0); - } + internal static void PrintModName(Color meloncolor, Color authorcolor, string name, string author, string additionalCredits, string version, string id) + { + PassLogMelonInfo(meloncolor, name, $"v{version}{(id == null ? "" : $" ({id})")}"); + PassLogMsg(authorcolor, $"by {author}", default, null); + + if (additionalCredits != null) + PassLogMsg(DefaultTextColor, $"Additional credits: {additionalCredits}", default, null); + } - internal static void PrintModName(Color meloncolor, Color authorcolor, string name, string author, string additionalCredits, string version, string id) + internal static unsafe void PassLogMsg(ColorRGB msgColor, string msg, ColorRGB sectionColor, string section) + { + if (section == null) { - PassLogMelonInfo(meloncolor, name, $"v{version}{(id == null ? "" : $" ({id})")}"); - PassLogMsg(authorcolor, $"by {author}", default, null); + fixed (char* pMsg = msg) + { + BootstrapInterop.Library.LogMsg(&msgColor, pMsg, msg.Length, null, null, 0); + } - if (additionalCredits != null) - PassLogMsg(DefaultTextColor, $"Additional credits: {additionalCredits}", default, null); + return; } - internal static unsafe void PassLogMsg(ColorRGB msgColor, string msg, ColorRGB sectionColor, string section) + fixed (char* pMsg = msg) { - if (section == null) + fixed (char* pSection = section) { - fixed (char* pMsg = msg) - { - BootstrapInterop.Library.LogMsg(&msgColor, pMsg, msg.Length, null, null, 0); - } - - return; + BootstrapInterop.Library.LogMsg(&msgColor, pMsg, msg.Length, §ionColor, pSection, section.Length); } + } + } + internal static unsafe void PassLogError(string msg, string section, bool warning) + { + if (section == null) + { fixed (char* pMsg = msg) { - fixed (char* pSection = section) - { - BootstrapInterop.Library.LogMsg(&msgColor, pMsg, msg.Length, §ionColor, pSection, section.Length); - } + BootstrapInterop.Library.LogError(pMsg, msg.Length, null, 0, warning); } + + return; } - internal static unsafe void PassLogError(string msg, string section, bool warning) + fixed (char* pMsg = msg) { - if (section == null) + fixed (char* pSection = section) { - fixed (char* pMsg = msg) - { - BootstrapInterop.Library.LogError(pMsg, msg.Length, null, 0, warning); - } - - return; - } - - fixed (char* pMsg = msg) - { - fixed (char* pSection = section) - { - BootstrapInterop.Library.LogError(pMsg, msg.Length, pSection, section.Length, warning); - } + BootstrapInterop.Library.LogError(pMsg, msg.Length, pSection, section.Length, warning); } } + } - internal static unsafe void PassLogMelonInfo(ColorRGB nameColor, string name, string info) + internal static unsafe void PassLogMelonInfo(ColorRGB nameColor, string name, string info) + { + fixed (char* pName = name) { - fixed (char* pName = name) + fixed (char* pInfo = info) { - fixed (char* pInfo = info) - { - BootstrapInterop.Library.LogMelonInfo(&nameColor, pName, name.Length, pInfo, info.Length); - } + BootstrapInterop.Library.LogMelonInfo(&nameColor, pName, name.Length, pInfo, info.Length); } } + } - [Obsolete("Log is obsolete. Please use Msg instead.")] - public static void Log(string txt) => Msg(txt); + [Obsolete("Log is obsolete. Please use Msg instead.")] + public static void Log(string txt) => Msg(txt); - [Obsolete("Log is obsolete. Please use Msg instead.")] - public static void Log(string txt, params object[] args) => Msg(txt, args); + [Obsolete("Log is obsolete. Please use Msg instead.")] + public static void Log(string txt, params object[] args) => Msg(txt, args); - [Obsolete("Log is obsolete. Please use Msg instead.")] - public static void Log(object obj) => Msg(obj); + [Obsolete("Log is obsolete. Please use Msg instead.")] + public static void Log(object obj) => Msg(obj); - [Obsolete("Log is obsolete. Please use Msg instead.")] - public static void Log(ConsoleColor color, string txt) => Msg(color, txt); + [Obsolete("Log is obsolete. Please use Msg instead.")] + public static void Log(ConsoleColor color, string txt) => Msg(color, txt); - [Obsolete("Log is obsolete. Please use Msg instead.")] - public static void Log(ConsoleColor color, string txt, params object[] args) => Msg(color, txt, args); + [Obsolete("Log is obsolete. Please use Msg instead.")] + public static void Log(ConsoleColor color, string txt, params object[] args) => Msg(color, txt, args); - [Obsolete("Log is obsolete. Please use Msg instead.")] - public static void Log(ConsoleColor color, object obj) => Msg(color, obj); + [Obsolete("Log is obsolete. Please use Msg instead.")] + public static void Log(ConsoleColor color, object obj) => Msg(color, obj); - [Obsolete("LogWarning is obsolete. Please use Warning instead.")] - public static void LogWarning(string txt) => Warning(txt); + [Obsolete("LogWarning is obsolete. Please use Warning instead.")] + public static void LogWarning(string txt) => Warning(txt); - [Obsolete("LogWarning is obsolete. Please use Warning instead.")] - public static void LogWarning(string txt, params object[] args) => Warning(txt, args); + [Obsolete("LogWarning is obsolete. Please use Warning instead.")] + public static void LogWarning(string txt, params object[] args) => Warning(txt, args); - [Obsolete("LogError is obsolete. Please use Error instead.")] - public static void LogError(string txt) => Error(txt); + [Obsolete("LogError is obsolete. Please use Error instead.")] + public static void LogError(string txt) => Error(txt); - [Obsolete("LogError is obsolete. Please use Error instead.")] - public static void LogError(string txt, params object[] args) => Error(txt, args); - } + [Obsolete("LogError is obsolete. Please use Error instead.")] + public static void LogError(string txt, params object[] args) => Error(txt, args); } \ No newline at end of file diff --git a/MelonLoader/MelonMod.cs b/MelonLoader/MelonMod.cs index a130bf0f0..8052d0c9c 100644 --- a/MelonLoader/MelonMod.cs +++ b/MelonLoader/MelonMod.cs @@ -2,99 +2,109 @@ using System.Collections.Generic; #pragma warning disable 0618 // Disabling the obsolete references warning to prevent the IDE going crazy when subscribing deprecated methods to some events in RegisterCallbacks -namespace MelonLoader +namespace MelonLoader; + +public abstract class MelonMod : MelonTypeBase { - public abstract class MelonMod : MelonTypeBase + static MelonMod() { - static MelonMod() - { - TypeName = "Mod"; - } + TypeName = "Mod"; + } - protected private override bool RegisterInternal() + private protected override bool RegisterInternal() + { + try { - try - { - OnPreSupportModule(); - } - catch (Exception ex) - { - MelonLogger.Error($"Failed to register {MelonTypeName} '{Location}': Melon failed to initialize in the deprecated OnPreSupportModule callback!"); - MelonLogger.Error(ex.ToString()); - return false; - } - - if (!base.RegisterInternal()) - return false; - - if (MelonEvents.MelonHarmonyInit.Disposed) - HarmonyInit(); - else - MelonEvents.MelonHarmonyInit.Subscribe(HarmonyInit, Priority, true); - - return true; + OnPreSupportModule(); } - private void HarmonyInit() + catch (Exception ex) { - if (!MelonAssembly.HarmonyDontPatchAll) - HarmonyInstance.PatchAll(MelonAssembly.Assembly); + MelonLogger.Error($"Failed to register {MelonTypeName} '{Location}': Melon failed to initialize in the deprecated OnPreSupportModule callback!"); + MelonLogger.Error(ex.ToString()); + return false; } - protected private override void RegisterCallbacks() - { - base.RegisterCallbacks(); + if (!base.RegisterInternal()) + return false; - MelonEvents.OnSceneWasLoaded.Subscribe(OnSceneWasLoaded, Priority); - MelonEvents.OnSceneWasInitialized.Subscribe(OnSceneWasInitialized, Priority); - MelonEvents.OnSceneWasUnloaded.Subscribe(OnSceneWasUnloaded, Priority); + if (MelonEvents.MelonHarmonyInit.Disposed) + HarmonyInit(); + else + MelonEvents.MelonHarmonyInit.Subscribe(HarmonyInit, Priority, true); - MelonEvents.OnSceneWasLoaded.Subscribe((idx, name) => OnLevelWasLoaded(idx), Priority); - MelonEvents.OnSceneWasInitialized.Subscribe((idx, name) => OnLevelWasInitialized(idx), Priority); - MelonEvents.OnApplicationStart.Subscribe(OnApplicationStart, Priority); - } + return true; + } + private void HarmonyInit() + { + if (!MelonAssembly.HarmonyDontPatchAll) + HarmonyInstance.PatchAll(MelonAssembly.Assembly); + } - #region Callbacks - - /// - /// Runs when a new Scene is loaded. - /// - public virtual void OnSceneWasLoaded(int buildIndex, string sceneName) { } - - /// - /// Runs once a Scene is initialized. - /// - public virtual void OnSceneWasInitialized(int buildIndex, string sceneName) { } - - /// - /// Runs once a Scene unloads. - /// - public virtual void OnSceneWasUnloaded(int buildIndex, string sceneName) { } - - #endregion - - #region Obsolete Members - [Obsolete("Override OnSceneWasLoaded instead.")] - public virtual void OnLevelWasLoaded(int level) { } - [Obsolete("Override OnSceneWasInitialized instead.")] - public virtual void OnLevelWasInitialized(int level) { } - - [Obsolete()] - private MelonModInfoAttribute _LegacyInfoAttribute = null; - [Obsolete("Use MelonBase.Info instead.")] - public MelonModInfoAttribute InfoAttribute { get { if (_LegacyInfoAttribute == null) _LegacyInfoAttribute = new MelonModInfoAttribute(Info.SystemType, Info.Name, Info.Version, Info.Author, Info.DownloadLink); return _LegacyInfoAttribute; } } - [Obsolete()] - private MelonModGameAttribute[] _LegacyGameAttributes = null; - [Obsolete("Use MelonBase.Games instead.")] - public MelonModGameAttribute[] GameAttributes { get { - if (_LegacyGameAttributes != null) - return _LegacyGameAttributes; - List newatts = new(); - foreach (MelonGameAttribute att in Games) - newatts.Add(new MelonModGameAttribute(att.Developer, att.Name)); - _LegacyGameAttributes = newatts.ToArray(); - return _LegacyGameAttributes; - } } + private protected override void RegisterCallbacks() + { + base.RegisterCallbacks(); + + MelonEvents.OnSceneWasLoaded.Subscribe(OnSceneWasLoaded, Priority); + MelonEvents.OnSceneWasInitialized.Subscribe(OnSceneWasInitialized, Priority); + MelonEvents.OnSceneWasUnloaded.Subscribe(OnSceneWasUnloaded, Priority); + + MelonEvents.OnSceneWasLoaded.Subscribe((idx, name) => OnLevelWasLoaded(idx), Priority); + MelonEvents.OnSceneWasInitialized.Subscribe((idx, name) => OnLevelWasInitialized(idx), Priority); + MelonEvents.OnApplicationStart.Subscribe(OnApplicationStart, Priority); + } - #endregion + #region Callbacks + + /// + /// Runs when a new Scene is loaded. + /// + public virtual void OnSceneWasLoaded(int buildIndex, string sceneName) { } + + /// + /// Runs once a Scene is initialized. + /// + public virtual void OnSceneWasInitialized(int buildIndex, string sceneName) { } + + /// + /// Runs once a Scene unloads. + /// + public virtual void OnSceneWasUnloaded(int buildIndex, string sceneName) { } + + #endregion + + #region Obsolete Members + [Obsolete("Override OnSceneWasLoaded instead.")] + public virtual void OnLevelWasLoaded(int level) { } + [Obsolete("Override OnSceneWasInitialized instead.")] + public virtual void OnLevelWasInitialized(int level) { } + + [Obsolete()] + private MelonModInfoAttribute _LegacyInfoAttribute = null; + [Obsolete("Use MelonBase.Info instead.")] + public MelonModInfoAttribute InfoAttribute + { + get + { + _LegacyInfoAttribute ??= new MelonModInfoAttribute(Info.SystemType, Info.Name, Info.Version, Info.Author, Info.DownloadLink); + return _LegacyInfoAttribute; + } } + [Obsolete()] + private MelonModGameAttribute[] _LegacyGameAttributes = null; + [Obsolete("Use MelonBase.Games instead.")] + public MelonModGameAttribute[] GameAttributes + { + get + { + if (_LegacyGameAttributes != null) + return _LegacyGameAttributes; + List newatts = []; + foreach (var att in Games) + newatts.Add(new MelonModGameAttribute(att.Developer, att.Name)); + _LegacyGameAttributes = newatts.ToArray(); + return _LegacyGameAttributes; + } + } + + #endregion } \ No newline at end of file diff --git a/MelonLoader/MelonOptionalDependenciesAttribute.cs b/MelonLoader/MelonOptionalDependenciesAttribute.cs index 8d09f2931..cbf126524 100644 --- a/MelonLoader/MelonOptionalDependenciesAttribute.cs +++ b/MelonLoader/MelonOptionalDependenciesAttribute.cs @@ -1,15 +1,14 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonOptionalDependenciesAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonOptionalDependenciesAttribute : Attribute - { - /// - /// The (simple) assembly names of the dependencies that should be regarded as optional. - /// - public string[] AssemblyNames { get; internal set; } + /// + /// The (simple) assembly names of the dependencies that should be regarded as optional. + /// + public string[] AssemblyNames { get; internal set; } - public MelonOptionalDependenciesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } - } + public MelonOptionalDependenciesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } } \ No newline at end of file diff --git a/MelonLoader/MelonPlatformAttribute.cs b/MelonLoader/MelonPlatformAttribute.cs index 9e36f3b00..629be9bd6 100644 --- a/MelonLoader/MelonPlatformAttribute.cs +++ b/MelonLoader/MelonPlatformAttribute.cs @@ -1,25 +1,24 @@ using System; using System.Linq; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonPlatformAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonPlatformAttribute : Attribute - { - public MelonPlatformAttribute(params CompatiblePlatforms[] platforms) => Platforms = platforms; + public MelonPlatformAttribute(params CompatiblePlatforms[] platforms) => Platforms = platforms; - // Enum for Melon Platform Compatibility. - public enum CompatiblePlatforms - { - UNIVERSAL, - WINDOWS_X86, - WINDOWS_X64 - }; + // Enum for Melon Platform Compatibility. + public enum CompatiblePlatforms + { + UNIVERSAL, + WINDOWS_X86, + WINDOWS_X64 + }; - // Platforms Compatible with the Melon. - public CompatiblePlatforms[] Platforms { get; internal set; } + // Platforms Compatible with the Melon. + public CompatiblePlatforms[] Platforms { get; internal set; } - public bool IsCompatible(CompatiblePlatforms platform) - => Platforms == null || Platforms.Length == 0 || Platforms.Contains(platform); - } + public bool IsCompatible(CompatiblePlatforms platform) + => Platforms == null || Platforms.Length == 0 || Platforms.Contains(platform); } \ No newline at end of file diff --git a/MelonLoader/MelonPlatformDomainAttribute.cs b/MelonLoader/MelonPlatformDomainAttribute.cs index 9447afb14..b360c13fc 100644 --- a/MelonLoader/MelonPlatformDomainAttribute.cs +++ b/MelonLoader/MelonPlatformDomainAttribute.cs @@ -1,24 +1,23 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonPlatformDomainAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonPlatformDomainAttribute : Attribute - { - public MelonPlatformDomainAttribute(CompatibleDomains domain = CompatibleDomains.UNIVERSAL) => Domain = domain; + public MelonPlatformDomainAttribute(CompatibleDomains domain = CompatibleDomains.UNIVERSAL) => Domain = domain; - // Enum for Melon Platform Domain Compatibility. - public enum CompatibleDomains - { - UNIVERSAL, - MONO, - IL2CPP - }; + // Enum for Melon Platform Domain Compatibility. + public enum CompatibleDomains + { + UNIVERSAL, + MONO, + IL2CPP + }; - // Platform Domain Compatibility of the Melon. - public CompatibleDomains Domain { get; internal set; } + // Platform Domain Compatibility of the Melon. + public CompatibleDomains Domain { get; internal set; } - public bool IsCompatible(CompatibleDomains domain) - => Domain == CompatibleDomains.UNIVERSAL || domain == CompatibleDomains.UNIVERSAL || Domain == domain; - } + public bool IsCompatible(CompatibleDomains domain) + => Domain == CompatibleDomains.UNIVERSAL || domain == CompatibleDomains.UNIVERSAL || Domain == domain; } \ No newline at end of file diff --git a/MelonLoader/MelonPlugin.cs b/MelonLoader/MelonPlugin.cs index 2bc890850..cada90e57 100644 --- a/MelonLoader/MelonPlugin.cs +++ b/MelonLoader/MelonPlugin.cs @@ -2,92 +2,98 @@ using System.Collections.Generic; #pragma warning disable 0618 // Disabling the obsolete references warning to prevent the IDE going crazy when subscribing deprecated methods to some events in RegisterCallbacks -namespace MelonLoader +namespace MelonLoader; + +public abstract class MelonPlugin : MelonTypeBase { - public abstract class MelonPlugin : MelonTypeBase + static MelonPlugin() { - static MelonPlugin() - { - TypeName = "Plugin"; - } + TypeName = "Plugin"; + } - protected private override void RegisterCallbacks() - { - base.RegisterCallbacks(); - - MelonEvents.OnPreInitialization.Subscribe(OnPreInitialization, Priority); - MelonEvents.OnApplicationEarlyStart.Subscribe(OnApplicationEarlyStart, Priority); - MelonEvents.OnPreModsLoaded.Subscribe(OnPreModsLoaded, Priority); - MelonEvents.OnPreModsLoaded.Subscribe(OnApplicationStart, Priority); - MelonEvents.OnApplicationStart.Subscribe(OnApplicationStarted, Priority); - MelonEvents.OnPreSupportModule.Subscribe(OnPreSupportModule, Priority); - } + private protected override void RegisterCallbacks() + { + base.RegisterCallbacks(); - protected private override bool RegisterInternal() - { - if (!base.RegisterInternal()) - return false; + MelonEvents.OnPreInitialization.Subscribe(OnPreInitialization, Priority); + MelonEvents.OnApplicationEarlyStart.Subscribe(OnApplicationEarlyStart, Priority); + MelonEvents.OnPreModsLoaded.Subscribe(OnPreModsLoaded, Priority); + MelonEvents.OnPreModsLoaded.Subscribe(OnApplicationStart, Priority); + MelonEvents.OnApplicationStart.Subscribe(OnApplicationStarted, Priority); + MelonEvents.OnPreSupportModule.Subscribe(OnPreSupportModule, Priority); + } - if (MelonEvents.MelonHarmonyEarlyInit.Disposed) - HarmonyInit(); - else - MelonEvents.MelonHarmonyEarlyInit.Subscribe(HarmonyInit, Priority, true); + private protected override bool RegisterInternal() + { + if (!base.RegisterInternal()) + return false; - return true; - } - private void HarmonyInit() - { - if (!MelonAssembly.HarmonyDontPatchAll) - HarmonyInstance.PatchAll(MelonAssembly.Assembly); - } + if (MelonEvents.MelonHarmonyEarlyInit.Disposed) + HarmonyInit(); + else + MelonEvents.MelonHarmonyEarlyInit.Subscribe(HarmonyInit, Priority, true); + + return true; + } + private void HarmonyInit() + { + if (!MelonAssembly.HarmonyDontPatchAll) + HarmonyInstance.PatchAll(MelonAssembly.Assembly); + } - #region Callbacks + #region Callbacks - /// - /// Runs before Game Initialization. - /// - public virtual void OnPreInitialization() { } + /// + /// Runs before Game Initialization. + /// + public virtual void OnPreInitialization() { } - /// - /// Runs after Game Initialization, before OnApplicationStart and before Assembly Generation on Il2Cpp games - /// - public virtual void OnApplicationEarlyStart() { } + /// + /// Runs after Game Initialization, before OnApplicationStart and before Assembly Generation on Il2Cpp games + /// + public virtual void OnApplicationEarlyStart() { } - /// - /// Runs before MelonMods from the Mods folder are loaded. - /// - public virtual void OnPreModsLoaded() { } + /// + /// Runs before MelonMods from the Mods folder are loaded. + /// + public virtual void OnPreModsLoaded() { } - /// - /// Runs after all MelonLoader components are fully initialized (including all MelonMods). - /// - public virtual void OnApplicationStarted() { } + /// + /// Runs after all MelonLoader components are fully initialized (including all MelonMods). + /// + public virtual void OnApplicationStarted() { } - #endregion + #endregion - #region Obsolete Members + #region Obsolete Members - [Obsolete()] - private MelonPluginInfoAttribute _LegacyInfoAttribute = null; - [Obsolete("MelonPlugin.InfoAttribute is obsolete. Please use MelonBase.Info instead.")] - public MelonPluginInfoAttribute InfoAttribute { get { if (_LegacyInfoAttribute == null) _LegacyInfoAttribute = new MelonPluginInfoAttribute(Info.SystemType, Info.Name, Info.Version, Info.Author, Info.DownloadLink); return _LegacyInfoAttribute; } } - [Obsolete()] - private MelonPluginGameAttribute[] _LegacyGameAttributes = null; - [Obsolete("MelonPlugin.GameAttributes is obsolete. Please use MelonBase.Games instead.")] - public MelonPluginGameAttribute[] GameAttributes + [Obsolete()] + private MelonPluginInfoAttribute _LegacyInfoAttribute = null; + [Obsolete("MelonPlugin.InfoAttribute is obsolete. Please use MelonBase.Info instead.")] + public MelonPluginInfoAttribute InfoAttribute + { + get { - get - { - if (_LegacyGameAttributes != null) - return _LegacyGameAttributes; - List newatts = new(); - foreach (MelonGameAttribute att in Games) - newatts.Add(new MelonPluginGameAttribute(att.Developer, att.Name)); - _LegacyGameAttributes = newatts.ToArray(); + _LegacyInfoAttribute ??= new MelonPluginInfoAttribute(Info.SystemType, Info.Name, Info.Version, Info.Author, Info.DownloadLink); + return _LegacyInfoAttribute; + } + } + [Obsolete()] + private MelonPluginGameAttribute[] _LegacyGameAttributes = null; + [Obsolete("MelonPlugin.GameAttributes is obsolete. Please use MelonBase.Games instead.")] + public MelonPluginGameAttribute[] GameAttributes + { + get + { + if (_LegacyGameAttributes != null) return _LegacyGameAttributes; - } + List newatts = []; + foreach (var att in Games) + newatts.Add(new MelonPluginGameAttribute(att.Developer, att.Name)); + _LegacyGameAttributes = newatts.ToArray(); + return _LegacyGameAttributes; } - - #endregion } + + #endregion } \ No newline at end of file diff --git a/MelonLoader/MelonPreferences.cs b/MelonLoader/MelonPreferences.cs index e77162ae7..1d784073c 100644 --- a/MelonLoader/MelonPreferences.cs +++ b/MelonLoader/MelonPreferences.cs @@ -1,371 +1,353 @@ -using System; +using MelonLoader.Preferences; +using MelonLoader.Utils; +using System; using System.Collections.Generic; using System.IO; -using MelonLoader.Preferences; -using MelonLoader.Utils; using Tomlet.Exceptions; -namespace MelonLoader +namespace MelonLoader; + +public static class MelonPreferences { - public static class MelonPreferences + public static readonly List Categories = []; + public static readonly List ReflectiveCategories = []; + public static readonly TomlMapper Mapper = new(); + + /// + /// Occurs when a Preferences File has been loaded. + /// + /// : Path of the Preferences File. + /// + /// + public static readonly MelonEvent OnPreferencesLoaded = new(); + + /// + /// Occurs when a Preferences File has been saved. + /// + /// : Path of the Preferences File. + /// + /// + public static readonly MelonEvent OnPreferencesSaved = new(); + + internal static List PrefFiles = []; + internal static Preferences.IO.File DefaultFile = null; + + static MelonPreferences() => DefaultFile = new Preferences.IO.File( + Path.Combine(MelonEnvironment.UserDataDirectory, "MelonPreferences.cfg"), + Path.Combine(MelonEnvironment.UserDataDirectory, "modprefs.ini")); + + public static void Load() { - public static readonly List Categories = new List(); - public static readonly List ReflectiveCategories = new List(); - public static readonly TomlMapper Mapper = new TomlMapper(); - - /// - /// Occurs when a Preferences File has been loaded. - /// - /// : Path of the Preferences File. - /// - /// - public static readonly MelonEvent OnPreferencesLoaded = new MelonEvent(); - - /// - /// Occurs when a Preferences File has been saved. - /// - /// : Path of the Preferences File. - /// - /// - public static readonly MelonEvent OnPreferencesSaved = new MelonEvent(); - - internal static List PrefFiles = new List(); - internal static Preferences.IO.File DefaultFile = null; - - static MelonPreferences() => DefaultFile = new Preferences.IO.File( - Path.Combine(MelonEnvironment.UserDataDirectory, "MelonPreferences.cfg"), - Path.Combine(MelonEnvironment.UserDataDirectory, "modprefs.ini")); - - public static void Load() + try { - try - { - DefaultFile.LegacyLoad(); - } - catch (Exception ex) - { - MelonLogger.Error($"Error while Loading Legacy Preferences from {DefaultFile.LegacyFilePath}: {ex}"); - DefaultFile.WasError = true; - } + DefaultFile.LegacyLoad(); + } + catch (Exception ex) + { + MelonLogger.Error($"Error while Loading Legacy Preferences from {DefaultFile.LegacyFilePath}: {ex}"); + DefaultFile.WasError = true; + } - try - { - DefaultFile.Load(); - } - catch (Exception ex) - { - MelonLogger.Error($"Error while Loading Preferences from {DefaultFile.FilePath}: {ex}"); - DefaultFile.WasError = true; - } + try + { + DefaultFile.Load(); + } + catch (Exception ex) + { + MelonLogger.Error($"Error while Loading Preferences from {DefaultFile.FilePath}: {ex}"); + DefaultFile.WasError = true; + } - if (PrefFiles.Count >= 0) + if (PrefFiles.Count >= 0) + { + foreach (var file in PrefFiles) { - foreach (Preferences.IO.File file in PrefFiles) + try { - try - { - file.Load(); - } - catch (Exception ex) - { - MelonLogger.Error($"Error while Loading Preferences from {file.FilePath}: {ex}"); - file.WasError = true; - } + file.Load(); } - } - - if (Categories.Count > 0) - { - foreach (MelonPreferences_Category category in Categories) + catch (Exception ex) { - if (category.Entries.Count <= 0) - continue; - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - if (currentFile.WasError) - continue; - foreach (MelonPreferences_Entry entry in category.Entries) - currentFile.SetupEntryFromRawValue(entry); + MelonLogger.Error($"Error while Loading Preferences from {file.FilePath}: {ex}"); + file.WasError = true; } } + } - if (ReflectiveCategories.Count > 0) + if (Categories.Count > 0) + { + foreach (var category in Categories) { - foreach (MelonPreferences_ReflectiveCategory category in ReflectiveCategories) - { - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - if (currentFile.WasError) - continue; - if (!(currentFile.TryGetCategoryTable(category.Identifier) is { } table)) - { - category.LoadDefaults(); - continue; - } - - category.Load(table); - - OnPreferencesLoaded.Invoke(currentFile.FilePath); - } + if (category.Entries.Count <= 0) + continue; + var currentFile = category.File; + currentFile ??= DefaultFile; + if (currentFile.WasError) + continue; + foreach (var entry in category.Entries) + currentFile.SetupEntryFromRawValue(entry); } - - MelonLogger.Msg("Preferences Loaded!"); } - public static void Save() + if (ReflectiveCategories.Count > 0) { - if (Categories.Count > 0) - { - foreach (MelonPreferences_Category category in Categories) - { - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - foreach (MelonPreferences_Entry entry in category.Entries) - if (!(entry.DontSaveDefault && entry.GetValueAsString() == entry.GetDefaultValueAsString()) && entry.GetValueAsString() != null) - currentFile.InsertIntoDocument(category.Identifier, entry.Identifier, entry.Save()); - } - } - if (ReflectiveCategories.Count > 0) + foreach (var category in ReflectiveCategories) { - foreach (MelonPreferences_ReflectiveCategory category in ReflectiveCategories) + var currentFile = category.File; + currentFile ??= DefaultFile; + if (currentFile.WasError) + continue; + if (currentFile.TryGetCategoryTable(category.Identifier) is not + { } table) { - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - currentFile.document.PutValue(category.Identifier, category.Save()); + category.LoadDefaults(); + continue; } + + category.Load(table); + + OnPreferencesLoaded.Invoke(currentFile.FilePath); } + } + + MelonLogger.Msg("Preferences Loaded!"); + } - try + public static void Save() + { + if (Categories.Count > 0) + { + foreach (var category in Categories) { - DefaultFile.Save(); + var currentFile = category.File; + currentFile ??= DefaultFile; + foreach (var entry in category.Entries) + if (!(entry.DontSaveDefault && entry.GetValueAsString() == entry.GetDefaultValueAsString()) && entry.GetValueAsString() != null) + currentFile.InsertIntoDocument(category.Identifier, entry.Identifier, entry.Save()); } - catch (Exception ex) + } + if (ReflectiveCategories.Count > 0) + { + foreach (var category in ReflectiveCategories) { - MelonLogger.Error($"Error while Saving Preferences to {DefaultFile.FilePath}: {ex}"); - DefaultFile.WasError = true; + var currentFile = category.File; + currentFile ??= DefaultFile; + currentFile.document.PutValue(category.Identifier, category.Save()); } + } + + try + { + DefaultFile.Save(); + } + catch (Exception ex) + { + MelonLogger.Error($"Error while Saving Preferences to {DefaultFile.FilePath}: {ex}"); + DefaultFile.WasError = true; + } - if (PrefFiles.Count >= 0) + if (PrefFiles.Count >= 0) + { + foreach (var file in PrefFiles) { - foreach (Preferences.IO.File file in PrefFiles) + try + { + file.Save(); + } + catch (Exception ex) { - try - { - file.Save(); - } - catch (Exception ex) - { - MelonLogger.Error($"Error while Saving Preferences to {file.FilePath}: {ex}"); - file.WasError = true; - continue; - } - OnPreferencesSaved.Invoke(file.FilePath); + MelonLogger.Error($"Error while Saving Preferences to {file.FilePath}: {ex}"); + file.WasError = true; + continue; } + OnPreferencesSaved.Invoke(file.FilePath); } - - MelonLogger.Msg("Preferences Saved!"); } - public static MelonPreferences_Category CreateCategory(string identifier) => CreateCategory(identifier, null, false); - public static MelonPreferences_Category CreateCategory(string identifier, string display_name = null) => CreateCategory(identifier, display_name, false); + MelonLogger.Msg("Preferences Saved!"); + } - public static MelonPreferences_Category CreateCategory(string identifier, string display_name = null, bool is_hidden = false, bool should_save = true) - { - if (string.IsNullOrEmpty(identifier)) - throw new Exception("identifier is null or empty when calling CreateCategory"); - if (display_name == null) - display_name = identifier; - MelonPreferences_Category category = GetCategory(identifier); - if (category != null) - return category; - return new MelonPreferences_Category(identifier, display_name, is_hidden); - } + public static MelonPreferences_Category CreateCategory(string identifier) => CreateCategory(identifier, null, false); + public static MelonPreferences_Category CreateCategory(string identifier, string display_name = null) => CreateCategory(identifier, display_name, false); - public static MelonPreferences_ReflectiveCategory CreateCategory(string identifier, string display_name = null) where T : new() => MelonPreferences_ReflectiveCategory.Create(identifier, display_name); + public static MelonPreferences_Category CreateCategory(string identifier, string display_name = null, bool is_hidden = false, bool should_save = true) + { + if (string.IsNullOrEmpty(identifier)) + throw new Exception("identifier is null or empty when calling CreateCategory"); + display_name ??= identifier; + var category = GetCategory(identifier); + return category != null ? category : new MelonPreferences_Category(identifier, display_name, is_hidden); + } - [Obsolete] - public static MelonPreferences_Entry CreateEntry(string category_identifier, string entry_identifier, - T default_value, string display_name, bool is_hidden) - => CreateEntry(category_identifier, entry_identifier, default_value, display_name, null, is_hidden, false, null); + public static MelonPreferences_ReflectiveCategory CreateCategory(string identifier, string display_name = null) where T : new() => MelonPreferences_ReflectiveCategory.Create(identifier, display_name); - public static MelonPreferences_Entry CreateEntry(string category_identifier, string entry_identifier, T default_value, - string display_name = null, string description = null, bool is_hidden = false, bool dont_save_default = false, - ValueValidator validator = null) - { - if (string.IsNullOrEmpty(category_identifier)) - throw new Exception("category_identifier is null or empty when calling CreateEntry"); + [Obsolete] + public static MelonPreferences_Entry CreateEntry(string category_identifier, string entry_identifier, + T default_value, string display_name, bool is_hidden) + => CreateEntry(category_identifier, entry_identifier, default_value, display_name, null, is_hidden, false, null); - if (string.IsNullOrEmpty(entry_identifier)) - throw new Exception("entry_identifier is null or empty when calling CreateEntry"); + public static MelonPreferences_Entry CreateEntry(string category_identifier, string entry_identifier, T default_value, + string display_name = null, string description = null, bool is_hidden = false, bool dont_save_default = false, + ValueValidator validator = null) + { + if (string.IsNullOrEmpty(category_identifier)) + throw new Exception("category_identifier is null or empty when calling CreateEntry"); - MelonPreferences_Category category = GetCategory(entry_identifier); - if (category == null) - category = CreateCategory(category_identifier); + if (string.IsNullOrEmpty(entry_identifier)) + throw new Exception("entry_identifier is null or empty when calling CreateEntry"); - return category.CreateEntry(entry_identifier, default_value, display_name, description, is_hidden, dont_save_default, validator); - } + var category = GetCategory(entry_identifier); + category ??= CreateCategory(category_identifier); - public static MelonPreferences_Category GetCategory(string identifier) - { - if (string.IsNullOrEmpty(identifier)) - throw new Exception("identifier is null or empty when calling GetCategory"); - if (Categories.Count <= 0) - return null; - return Categories.Find(x => x.Identifier.Equals(identifier)); - } + return category.CreateEntry(entry_identifier, default_value, display_name, description, is_hidden, dont_save_default, validator); + } - public static T GetCategory(string identifier) where T : new() - { - if (string.IsNullOrEmpty(identifier)) - throw new Exception("identifier is null or empty when calling GetCategory"); - if (ReflectiveCategories.Count <= 0) - return default; - MelonPreferences_ReflectiveCategory category = ReflectiveCategories.Find(x => x.Identifier.Equals(identifier)); - if (category != null) - return category.GetValue(); + public static MelonPreferences_Category GetCategory(string identifier) + { + if (string.IsNullOrEmpty(identifier)) + throw new Exception("identifier is null or empty when calling GetCategory"); + return Categories.Count <= 0 ? null : Categories.Find(x => x.Identifier.Equals(identifier)); + } + + public static T GetCategory(string identifier) where T : new() + { + if (string.IsNullOrEmpty(identifier)) + throw new Exception("identifier is null or empty when calling GetCategory"); + if (ReflectiveCategories.Count <= 0) return default; - } + var category = ReflectiveCategories.Find(x => x.Identifier.Equals(identifier)); + return category != null ? category.GetValue() : default; + } - public static void SaveCategory(string identifier, bool printmsg = true) - { - if (string.IsNullOrEmpty(identifier)) - throw new Exception("identifier is null or empty when calling GetCategory"); - if (ReflectiveCategories.Count <= 0) - return; - MelonPreferences_ReflectiveCategory category = ReflectiveCategories.Find(x => x.Identifier.Equals(identifier)); - if (category != null) - category.SaveToFile(printmsg); - } + public static void SaveCategory(string identifier, bool printmsg = true) + { + if (string.IsNullOrEmpty(identifier)) + throw new Exception("identifier is null or empty when calling GetCategory"); + if (ReflectiveCategories.Count <= 0) + return; + var category = ReflectiveCategories.Find(x => x.Identifier.Equals(identifier)); + category?.SaveToFile(printmsg); + } - public static MelonPreferences_Entry GetEntry(string category_identifier, string entry_identifier) => GetCategory(category_identifier)?.GetEntry(entry_identifier); - public static MelonPreferences_Entry GetEntry(string category_identifier, string entry_identifier) => GetCategory(category_identifier)?.GetEntry(entry_identifier); - public static bool HasEntry(string category_identifier, string entry_identifier) => (GetEntry(category_identifier, entry_identifier) != null); + public static MelonPreferences_Entry GetEntry(string category_identifier, string entry_identifier) => GetCategory(category_identifier)?.GetEntry(entry_identifier); + public static MelonPreferences_Entry GetEntry(string category_identifier, string entry_identifier) => GetCategory(category_identifier)?.GetEntry(entry_identifier); + public static bool HasEntry(string category_identifier, string entry_identifier) => GetEntry(category_identifier, entry_identifier) != null; - public static void SetEntryValue(string category_identifier, string entry_identifier, T value) - { - var entry = GetCategory(category_identifier)?.GetEntry(entry_identifier); - if (entry != null) entry.Value = value; - } + public static void SetEntryValue(string category_identifier, string entry_identifier, T value) + { + var entry = GetCategory(category_identifier)?.GetEntry(entry_identifier); + if (entry != null) + entry.Value = value; + } - public static T GetEntryValue(string category_identifier, string entry_identifier) + public static T GetEntryValue(string category_identifier, string entry_identifier) + { + var cat = GetCategory(category_identifier); + if (cat == null) + return default; + var entry = cat.GetEntry(entry_identifier); + return entry == null ? default : entry.Value; + } + + internal static Preferences.IO.File GetPrefFileFromFilePath(string filepath) + { + if (PrefFiles.Count <= 0) + return null; + var filepathinfo = new FileInfo(filepath); + foreach (var file in PrefFiles) { - MelonPreferences_Category cat = GetCategory(category_identifier); - if (cat == null) - return default; - var entry = cat.GetEntry(entry_identifier); - if (entry == null) - return default; - return entry.Value; + var filepathinfo2 = new FileInfo(file.FilePath); + if (filepathinfo.FullName.Equals(filepathinfo2.FullName)) + return file; } - internal static Preferences.IO.File GetPrefFileFromFilePath(string filepath) - { - if (PrefFiles.Count <= 0) - return null; - FileInfo filepathinfo = new FileInfo(filepath); - foreach (Preferences.IO.File file in PrefFiles) - { - FileInfo filepathinfo2 = new FileInfo(file.FilePath); - if (filepathinfo.FullName.Equals(filepathinfo2.FullName)) - return file; - } + return null; + } - return null; + internal static bool IsFileInUse(Preferences.IO.File file) + { + if (Categories.Count <= 0) + return false; + file ??= DefaultFile; + if (file == DefaultFile) + return true; + foreach (var category in Categories) + { + var currentFile = category.File; + currentFile ??= DefaultFile; + if (currentFile == file) + return true; } - internal static bool IsFileInUse(Preferences.IO.File file) + foreach (var category in ReflectiveCategories) { - if (Categories.Count <= 0) - return false; - if (file == null) - file = DefaultFile; - if (file == DefaultFile) + var currentFile = category.File; + currentFile ??= DefaultFile; + if (currentFile == file) return true; - foreach (MelonPreferences_Category category in Categories) - { - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - if (currentFile == file) - return true; - } + } - foreach (MelonPreferences_ReflectiveCategory category in ReflectiveCategories) - { - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - if (currentFile == file) - return true; - } + return false; + } - return false; + internal static void LoadFileAndRefreshCategories(Preferences.IO.File file, bool printmsg = true) + { + try + { + file.Load(); } - - internal static void LoadFileAndRefreshCategories(Preferences.IO.File file, bool printmsg = true) + catch (TomlUnescapedUnicodeControlCharException ex) { - try - { - file.Load(); - } - catch (TomlUnescapedUnicodeControlCharException ex) - { - MelonLogger.Error($"Error while Loading Preferences from {file.FilePath}: {ex}"); - return; - } - catch (Exception ex) + MelonLogger.Error($"Error while Loading Preferences from {file.FilePath}: {ex}"); + return; + } + catch (Exception ex) + { + MelonLogger.Error($"Error while Loading Preferences from {file.FilePath}: {ex}"); + file.WasError = true; + return; + } + + if (Categories.Count > 0) + foreach (var category in Categories) { - MelonLogger.Error($"Error while Loading Preferences from {file.FilePath}: {ex}"); - file.WasError = true; - return; + var currentFile = category.File; + currentFile ??= DefaultFile; + if ((currentFile != file) || (category.Entries.Count <= 0)) + continue; + foreach (var entry in category.Entries) + currentFile.SetupEntryFromRawValue(entry); } - if (Categories.Count > 0) - foreach (MelonPreferences_Category category in Categories) - { - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - if ((currentFile != file) || (category.Entries.Count <= 0)) - continue; - foreach (MelonPreferences_Entry entry in category.Entries) - currentFile.SetupEntryFromRawValue(entry); - } - - if (ReflectiveCategories.Count > 0) - foreach (MelonPreferences_ReflectiveCategory category in ReflectiveCategories) + if (ReflectiveCategories.Count > 0) + foreach (var category in ReflectiveCategories) + { + var currentFile = category.File; + currentFile ??= DefaultFile; + if (currentFile != file) + continue; + if (file.TryGetCategoryTable(category.Identifier) is not + { } table) { - Preferences.IO.File currentFile = category.File; - if (currentFile == null) - currentFile = DefaultFile; - if (currentFile != file) - continue; - if (!(file.TryGetCategoryTable(category.Identifier) is { } table)) - { - category.LoadDefaults(); - continue; - } - category.Load(table); + category.LoadDefaults(); + continue; } + category.Load(table); + } - if (printmsg) - MelonLogger.MsgDirect($"MelonPreferences Loaded from {file.FilePath}"); - - OnPreferencesLoaded.Invoke(file.FilePath); - } + if (printmsg) + MelonLogger.MsgDirect($"MelonPreferences Loaded from {file.FilePath}"); - public static void RemoveCategoryFromFile(string filePath, string categoryName) - { - Preferences.IO.File currentFile = GetPrefFileFromFilePath(filePath); - if (currentFile == null) - return; - currentFile.RemoveCategoryFromDocument(categoryName); - } + OnPreferencesLoaded.Invoke(file.FilePath); + } - internal static bool IsFilePathDefault(string filepath) => new FileInfo(filepath).FullName.Equals(new FileInfo(DefaultFile.FilePath).FullName); + public static void RemoveCategoryFromFile(string filePath, string categoryName) + { + var currentFile = GetPrefFileFromFilePath(filePath); + if (currentFile == null) + return; + currentFile.RemoveCategoryFromDocument(categoryName); } + + internal static bool IsFilePathDefault(string filepath) => new FileInfo(filepath).FullName.Equals(new FileInfo(DefaultFile.FilePath).FullName); } \ No newline at end of file diff --git a/MelonLoader/MelonPreferences_Category.cs b/MelonLoader/MelonPreferences_Category.cs index b45ab8244..6a29488f3 100644 --- a/MelonLoader/MelonPreferences_Category.cs +++ b/MelonLoader/MelonPreferences_Category.cs @@ -1,188 +1,179 @@ using System; using System.Collections.Generic; -namespace MelonLoader +namespace MelonLoader; + +public class MelonPreferences_Category { - public class MelonPreferences_Category - { - public readonly List Entries = new List(); - internal Preferences.IO.File File = null; + public readonly List Entries = []; + internal Preferences.IO.File File = null; - public string Identifier { get; internal set; } - public string DisplayName { get; set; } - public bool IsHidden { get; set; } - public bool IsInlined { get; set; } + public string Identifier { get; internal set; } + public string DisplayName { get; set; } + public bool IsHidden { get; set; } + public bool IsInlined { get; set; } - internal MelonPreferences_Category(string identifier, string display_name, bool is_hidden = false, bool is_inlined = false) - { - Identifier = identifier; - DisplayName = display_name; - IsHidden = is_hidden; - IsInlined = is_inlined; - MelonPreferences.Categories.Add(this); - } - - public MelonPreferences_Entry CreateEntry(string identifier, T default_value, string display_name, bool is_hidden) - => CreateEntry(identifier, default_value, display_name, null, is_hidden, false, null, null); - public MelonPreferences_Entry CreateEntry(string identifier, T default_value, string display_name, - string description, bool is_hidden, bool dont_save_default, Preferences.ValueValidator validator) - => CreateEntry(identifier, default_value, display_name, description, is_hidden, dont_save_default, validator, null); - public MelonPreferences_Entry CreateEntry(string identifier, T default_value, string display_name = null, - string description = null, bool is_hidden = false, bool dont_save_default = false, Preferences.ValueValidator validator = null, string oldIdentifier = null) - { - if (string.IsNullOrEmpty(identifier)) - throw new Exception("identifier is null or empty when calling CreateEntry"); + internal MelonPreferences_Category(string identifier, string display_name, bool is_hidden = false, bool is_inlined = false) + { + Identifier = identifier; + DisplayName = display_name; + IsHidden = is_hidden; + IsInlined = is_inlined; + MelonPreferences.Categories.Add(this); + } - if (display_name == null) - display_name = identifier; + public MelonPreferences_Entry CreateEntry(string identifier, T default_value, string display_name, bool is_hidden) + => CreateEntry(identifier, default_value, display_name, null, is_hidden, false, null, null); + public MelonPreferences_Entry CreateEntry(string identifier, T default_value, string display_name, + string description, bool is_hidden, bool dont_save_default, Preferences.ValueValidator validator) + => CreateEntry(identifier, default_value, display_name, description, is_hidden, dont_save_default, validator, null); + public MelonPreferences_Entry CreateEntry(string identifier, T default_value, string display_name = null, + string description = null, bool is_hidden = false, bool dont_save_default = false, Preferences.ValueValidator validator = null, string oldIdentifier = null) + { + if (string.IsNullOrEmpty(identifier)) + throw new Exception("identifier is null or empty when calling CreateEntry"); - var entry = GetEntry(identifier); - if (entry != null) - throw new Exception($"Calling CreateEntry for { display_name } when it Already Exists"); + display_name ??= identifier; - if (validator != null && !validator.IsValid(default_value)) - throw new ArgumentException($"Default value '{default_value}' is invalid according to the provided ValueValidator!"); + var entry = GetEntry(identifier); + if (entry != null) + throw new Exception($"Calling CreateEntry for {display_name} when it Already Exists"); - if (oldIdentifier != null) - { - if (HasEntry(oldIdentifier)) - throw new Exception($"Unable to rename '{oldIdentifier}' when it got already loaded"); + if (validator != null && !validator.IsValid(default_value)) + throw new ArgumentException($"Default value '{default_value}' is invalid according to the provided ValueValidator!"); - RenameEntry(oldIdentifier, identifier); - } + if (oldIdentifier != null) + { + if (HasEntry(oldIdentifier)) + throw new Exception($"Unable to rename '{oldIdentifier}' when it got already loaded"); - entry = new MelonPreferences_Entry - { - Identifier = identifier, - DisplayName = display_name, - Description = description, - IsHidden = is_hidden, - DontSaveDefault = dont_save_default, - Category = this, - DefaultValue = default_value, - Value = default_value, - Validator = validator, - }; - - Preferences.IO.File currentFile = File; - if (currentFile == null) - currentFile = MelonPreferences.DefaultFile; - currentFile.SetupEntryFromRawValue(entry); - - Entries.Add(entry); - - return entry; + RenameEntry(oldIdentifier, identifier); } - public bool DeleteEntry(string identifier) + entry = new MelonPreferences_Entry { - MelonPreferences_Entry entry = GetEntry(identifier); - if (entry != null) - Entries.Remove(entry); + Identifier = identifier, + DisplayName = display_name, + Description = description, + IsHidden = is_hidden, + DontSaveDefault = dont_save_default, + Category = this, + DefaultValue = default_value, + Value = default_value, + Validator = validator, + }; + + var currentFile = File; + currentFile ??= MelonPreferences.DefaultFile; + currentFile.SetupEntryFromRawValue(entry); + + Entries.Add(entry); + + return entry; + } - Preferences.IO.File currentfile = File; - if (currentfile == null) - currentfile = MelonPreferences.DefaultFile; + public bool DeleteEntry(string identifier) + { + var entry = GetEntry(identifier); + if (entry != null) + Entries.Remove(entry); - return currentfile.RemoveEntryFromDocument(Identifier, identifier); - } + var currentfile = File; + currentfile ??= MelonPreferences.DefaultFile; - public bool RenameEntry(string identifier, string newIdentifier) - { - MelonPreferences_Entry entry = GetEntry(identifier); - if (entry != null) - entry.Identifier = newIdentifier; + return currentfile.RemoveEntryFromDocument(Identifier, identifier); + } - Preferences.IO.File currentfile = File; - if (currentfile == null) - currentfile = MelonPreferences.DefaultFile; + public bool RenameEntry(string identifier, string newIdentifier) + { + var entry = GetEntry(identifier); + if (entry != null) + entry.Identifier = newIdentifier; - return currentfile.RenameEntryInDocument(Identifier, identifier, newIdentifier); - } + var currentfile = File; + currentfile ??= MelonPreferences.DefaultFile; - public MelonPreferences_Entry GetEntry(string identifier) - { - if (string.IsNullOrEmpty(identifier)) - throw new Exception("identifier cannot be null or empty when calling GetEntry"); - if (Entries.Count <= 0) - return null; - return Entries.Find(x => x.Identifier.Equals(identifier)); - } - public MelonPreferences_Entry GetEntry(string identifier) => (MelonPreferences_Entry)GetEntry(identifier); - public bool HasEntry(string identifier) => GetEntry(identifier) != null; + return currentfile.RenameEntryInDocument(Identifier, identifier, newIdentifier); + } - public void SetFilePath(string filepath) => SetFilePath(filepath, true, true); - public void SetFilePath(string filepath, bool autoload) => SetFilePath(filepath, autoload, true); - public void SetFilePath(string filepath, bool autoload, bool printmsg) - { - if (File != null) - { - Preferences.IO.File oldfile = File; - File = null; - if (!MelonPreferences.IsFileInUse(oldfile)) - { - oldfile.FileWatcher.Destroy(); - MelonPreferences.PrefFiles.Remove(oldfile); - } - } - if (!string.IsNullOrEmpty(filepath) && !MelonPreferences.IsFilePathDefault(filepath)) - { - File = MelonPreferences.GetPrefFileFromFilePath(filepath); - if (File == null) - { - File = new Preferences.IO.File(filepath); - MelonPreferences.PrefFiles.Add(File); - } - } - if (autoload) - MelonPreferences.LoadFileAndRefreshCategories(File, printmsg); - } + public MelonPreferences_Entry GetEntry(string identifier) + { + if (string.IsNullOrEmpty(identifier)) + throw new Exception("identifier cannot be null or empty when calling GetEntry"); + return Entries.Count <= 0 ? null : Entries.Find(x => x.Identifier.Equals(identifier)); + } + public MelonPreferences_Entry GetEntry(string identifier) => (MelonPreferences_Entry)GetEntry(identifier); + public bool HasEntry(string identifier) => GetEntry(identifier) != null; - public void ResetFilePath() + public void SetFilePath(string filepath) => SetFilePath(filepath, true, true); + public void SetFilePath(string filepath, bool autoload) => SetFilePath(filepath, autoload, true); + public void SetFilePath(string filepath, bool autoload, bool printmsg) + { + if (File != null) { - if (File == null) - return; - Preferences.IO.File oldfile = File; + var oldfile = File; File = null; if (!MelonPreferences.IsFileInUse(oldfile)) { oldfile.FileWatcher.Destroy(); MelonPreferences.PrefFiles.Remove(oldfile); } - MelonPreferences.LoadFileAndRefreshCategories(MelonPreferences.DefaultFile); } - - public void SaveToFile(bool printmsg = true) + if (!string.IsNullOrEmpty(filepath) && !MelonPreferences.IsFilePathDefault(filepath)) { - Preferences.IO.File currentfile = File; - if (currentfile == null) - currentfile = MelonPreferences.DefaultFile; - foreach (MelonPreferences_Entry entry in Entries) - if (!(entry.DontSaveDefault && entry.GetValueAsString() == entry.GetDefaultValueAsString()) && entry.GetValueAsString() != null) - currentfile.InsertIntoDocument(Identifier, entry.Identifier, entry.Save(), IsInlined); - try - { - currentfile.Save(); - } - catch (Exception ex) + File = MelonPreferences.GetPrefFileFromFilePath(filepath); + if (File == null) { - MelonLogger.Error($"Error while Saving Preferences to {currentfile.FilePath}: {ex}"); - currentfile.WasError = true; + File = new Preferences.IO.File(filepath); + MelonPreferences.PrefFiles.Add(File); } - if (printmsg) - MelonLogger.Msg($"MelonPreferences Saved to {currentfile.FilePath}"); + } + if (autoload) + MelonPreferences.LoadFileAndRefreshCategories(File, printmsg); + } - MelonPreferences.OnPreferencesSaved.Invoke(currentfile.FilePath); + public void ResetFilePath() + { + if (File == null) + return; + var oldfile = File; + File = null; + if (!MelonPreferences.IsFileInUse(oldfile)) + { + oldfile.FileWatcher.Destroy(); + MelonPreferences.PrefFiles.Remove(oldfile); } + MelonPreferences.LoadFileAndRefreshCategories(MelonPreferences.DefaultFile); + } - public void LoadFromFile(bool printmsg = true) + public void SaveToFile(bool printmsg = true) + { + var currentfile = File; + currentfile ??= MelonPreferences.DefaultFile; + foreach (var entry in Entries) + if (!(entry.DontSaveDefault && entry.GetValueAsString() == entry.GetDefaultValueAsString()) && entry.GetValueAsString() != null) + currentfile.InsertIntoDocument(Identifier, entry.Identifier, entry.Save(), IsInlined); + try { - Preferences.IO.File currentfile = File; - if (currentfile == null) - currentfile = MelonPreferences.DefaultFile; - MelonPreferences.LoadFileAndRefreshCategories(currentfile, printmsg); + currentfile.Save(); } + catch (Exception ex) + { + MelonLogger.Error($"Error while Saving Preferences to {currentfile.FilePath}: {ex}"); + currentfile.WasError = true; + } + if (printmsg) + MelonLogger.Msg($"MelonPreferences Saved to {currentfile.FilePath}"); + + MelonPreferences.OnPreferencesSaved.Invoke(currentfile.FilePath); + } - public void DestroyFileWatcher() => File?.FileWatcher.Destroy(); + public void LoadFromFile(bool printmsg = true) + { + var currentfile = File; + currentfile ??= MelonPreferences.DefaultFile; + MelonPreferences.LoadFileAndRefreshCategories(currentfile, printmsg); } + + public void DestroyFileWatcher() => File?.FileWatcher.Destroy(); } diff --git a/MelonLoader/MelonPreferences_Entry.cs b/MelonLoader/MelonPreferences_Entry.cs index d0d4e8cc3..1eb2f1db2 100644 --- a/MelonLoader/MelonPreferences_Entry.cs +++ b/MelonLoader/MelonPreferences_Entry.cs @@ -3,124 +3,126 @@ using Tomlet.Exceptions; using Tomlet.Models; -namespace MelonLoader -{ - public abstract class MelonPreferences_Entry - { - public string Identifier { get; internal set; } - public string DisplayName { get; set; } - public string Description { get; set; } - public string Comment { get; set; } - public bool IsHidden { get; set; } - public bool DontSaveDefault { get; set; } - public MelonPreferences_Category Category { get; internal set; } +namespace MelonLoader; - public abstract object BoxedValue { get; set; } - public abstract object BoxedEditedValue { get; set; } +public abstract class MelonPreferences_Entry +{ + public string Identifier { get; internal set; } + public string DisplayName { get; set; } + public string Description { get; set; } + public string Comment { get; set; } + public bool IsHidden { get; set; } + public bool DontSaveDefault { get; set; } + public MelonPreferences_Category Category { get; internal set; } - public Preferences.ValueValidator Validator { get; internal set; } + public abstract object BoxedValue { get; set; } + public abstract object BoxedEditedValue { get; set; } - public string GetExceptionMessage(string submsg) - => $"Attempted to {submsg} {DisplayName} when it is a {GetReflectedType().FullName}!"; + public Preferences.ValueValidator Validator { get; internal set; } - public abstract Type GetReflectedType(); + public string GetExceptionMessage(string submsg) + => $"Attempted to {submsg} {DisplayName} when it is a {GetReflectedType().FullName}!"; - public abstract void ResetToDefault(); + public abstract Type GetReflectedType(); - public abstract string GetEditedValueAsString(); - public abstract string GetDefaultValueAsString(); - public abstract string GetValueAsString(); + public abstract void ResetToDefault(); - public abstract void Load(TomlValue obj); - public abstract TomlValue Save(); + public abstract string GetEditedValueAsString(); + public abstract string GetDefaultValueAsString(); + public abstract string GetValueAsString(); - public readonly MelonEvent OnEntryValueChangedUntyped = new MelonEvent(); - protected void FireUntypedValueChanged(object old, object neew) - { - OnEntryValueChangedUntyped.Invoke(old, neew); - OnValueChangedUntyped?.Invoke(); - } + public abstract void Load(TomlValue obj); + public abstract TomlValue Save(); - [Obsolete("Please use the OnEntryValueChangedUntyped MelonEvent instead.")] - public event Action OnValueChangedUntyped; + public readonly MelonEvent OnEntryValueChangedUntyped = new(); + protected void FireUntypedValueChanged(object old, object neew) + { + OnEntryValueChangedUntyped.Invoke(old, neew); + OnValueChangedUntyped?.Invoke(); } - public class MelonPreferences_Entry : MelonPreferences_Entry + [Obsolete("Please use the OnEntryValueChangedUntyped MelonEvent instead.")] + public event Action OnValueChangedUntyped; +} + +public class MelonPreferences_Entry : MelonPreferences_Entry +{ + private T myValue; + public T Value { - private T myValue; - public T Value + get => myValue; + set { - get => myValue; - set - { - if (Validator != null) - value = (T)Validator.EnsureValid(value); - - if ((myValue == null && value == null) || (myValue != null && myValue.Equals(value))) - return; - - var old = myValue; - myValue = value; - EditedValue = myValue; - OnEntryValueChanged.Invoke(old, value); - OnValueChanged?.Invoke(old, value); - FireUntypedValueChanged(old, value); - } - } + if (Validator != null) + value = (T)Validator.EnsureValid(value); - public T EditedValue { get; set; } - public T DefaultValue { get; set; } + if ((myValue == null && value == null) || (myValue != null && myValue.Equals(value))) + return; - public override object BoxedValue - { - get => myValue; - set => Value = (T)value; + var old = myValue; + myValue = value; + EditedValue = myValue; + OnEntryValueChanged.Invoke(old, value); + OnValueChanged?.Invoke(old, value); + FireUntypedValueChanged(old, value); } + } - public override object BoxedEditedValue - { - get => EditedValue; - set => EditedValue = (T)value; - } + public T EditedValue { get; set; } + public T DefaultValue { get; set; } + + public override object BoxedValue + { + get => myValue; + set => Value = (T)value; + } + + public override object BoxedEditedValue + { + get => EditedValue; + set => EditedValue = (T)value; + } - public override void ResetToDefault() => Value = DefaultValue; + public override void ResetToDefault() => Value = DefaultValue; - public readonly MelonEvent OnEntryValueChanged = new MelonEvent(); + public readonly MelonEvent OnEntryValueChanged = new(); - [Obsolete("Please use the OnEntryValueChanged MelonEvent instead.")] - public event Action OnValueChanged; + [Obsolete("Please use the OnEntryValueChanged MelonEvent instead.")] + public event Action OnValueChanged; - public override Type GetReflectedType() => typeof(T); + public override Type GetReflectedType() => typeof(T); - public override string GetEditedValueAsString() => EditedValue?.ToString(); - public override string GetDefaultValueAsString() => DefaultValue?.ToString(); - public override string GetValueAsString() => Value?.ToString(); + public override string GetEditedValueAsString() => EditedValue?.ToString(); + public override string GetDefaultValueAsString() => DefaultValue?.ToString(); + public override string GetValueAsString() => Value?.ToString(); - public override void Load(TomlValue obj) + public override void Load(TomlValue obj) + { + try { - try { Value = TomletMain.To(obj); } - catch (TomlTypeMismatchException) - { - return; - } - catch (TomlNoSuchValueException) - { - return; - } - catch (TomlEnumParseException) - { - return; - } + Value = TomletMain.To(obj); + } + catch (TomlTypeMismatchException) + { + return; } - public override TomlValue Save() + catch (TomlNoSuchValueException) { - Value = EditedValue; - TomlValue returnval = TomletMain.ValueFrom(Value); - returnval.Comments.PrecedingComment = Description; - returnval.Comments.InlineComment = Comment; - if (!string.IsNullOrEmpty(returnval.Comments.InlineComment)) - returnval.Comments.InlineComment.Replace('\n', ' '); - return returnval; + return; } + catch (TomlEnumParseException) + { + return; + } + } + public override TomlValue Save() + { + Value = EditedValue; + var returnval = TomletMain.ValueFrom(Value); + returnval.Comments.PrecedingComment = Description; + returnval.Comments.InlineComment = Comment; + if (!string.IsNullOrEmpty(returnval.Comments.InlineComment)) + returnval.Comments.InlineComment.Replace('\n', ' '); + return returnval; } } \ No newline at end of file diff --git a/MelonLoader/MelonPriorityAttribute.cs b/MelonLoader/MelonPriorityAttribute.cs index 3820756d3..e04066bfe 100644 --- a/MelonLoader/MelonPriorityAttribute.cs +++ b/MelonLoader/MelonPriorityAttribute.cs @@ -1,15 +1,14 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MelonPriorityAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - public class MelonPriorityAttribute : Attribute - { - /// - /// Priority of the Melon. - /// - public int Priority; + /// + /// Priority of the Melon. + /// + public int Priority; - public MelonPriorityAttribute(int priority = 0) => Priority = priority; - } + public MelonPriorityAttribute(int priority = 0) => Priority = priority; } \ No newline at end of file diff --git a/MelonLoader/MelonProcessAttribute.cs b/MelonLoader/MelonProcessAttribute.cs index 16762ac53..51444b2d3 100644 --- a/MelonLoader/MelonProcessAttribute.cs +++ b/MelonLoader/MelonProcessAttribute.cs @@ -1,32 +1,31 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class MelonProcessAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class MelonProcessAttribute : Attribute - { - public MelonProcessAttribute(string exe_name = null) - => EXE_Name = RemoveExtension(exe_name); + public MelonProcessAttribute(string exe_name = null) + => EXE_Name = RemoveExtension(exe_name); - /// - /// Name of the Game's Executable without the '.exe' extension. - /// - public string EXE_Name { get; internal set; } + /// + /// Name of the Game's Executable without the '.exe' extension. + /// + public string EXE_Name { get; internal set; } - /// - /// If the Attribute is set as Universal or not. - /// - public bool Universal - => string.IsNullOrEmpty(EXE_Name); + /// + /// If the Attribute is set as Universal or not. + /// + public bool Universal + => string.IsNullOrEmpty(EXE_Name); - /// - /// Checks if the Attribute is compatible with or not. - /// - public bool IsCompatible(string processName) - => Universal || string.IsNullOrEmpty(processName) || (RemoveExtension(processName) == EXE_Name); + /// + /// Checks if the Attribute is compatible with or not. + /// + public bool IsCompatible(string processName) + => Universal || string.IsNullOrEmpty(processName) || (RemoveExtension(processName) == EXE_Name); - private string RemoveExtension(string name) - => name == null ? null : (name.EndsWith(".exe") ? name.Remove(name.Length - 4) : name); + private string RemoveExtension(string name) + => name == null ? null : (name.EndsWith(".exe") ? name.Remove(name.Length - 4) : name); - } } \ No newline at end of file diff --git a/MelonLoader/MelonTypeBase.cs b/MelonLoader/MelonTypeBase.cs index 8fbf47223..0bd27d3db 100644 --- a/MelonLoader/MelonTypeBase.cs +++ b/MelonLoader/MelonTypeBase.cs @@ -1,42 +1,41 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -namespace MelonLoader +namespace MelonLoader; + +public abstract class MelonTypeBase : MelonBase where T : MelonTypeBase { - public abstract class MelonTypeBase : MelonBase where T : MelonTypeBase + /// + /// List of registered s. + /// + public static new ReadOnlyCollection RegisteredMelons => _registeredMelons.AsReadOnly(); + internal static new List _registeredMelons = []; + + /// + /// A Human-Readable Name for . + /// + public static string TypeName { get; protected internal set; } + + static MelonTypeBase() { - /// - /// List of registered s. - /// - new public static ReadOnlyCollection RegisteredMelons => _registeredMelons.AsReadOnly(); - new internal static List _registeredMelons = new(); - - /// - /// A Human-Readable Name for . - /// - public static string TypeName { get; protected internal set; } - - static MelonTypeBase() - { - System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // To make sure that the type initializer of T was triggered. - } - - public sealed override string MelonTypeName => TypeName; - - protected private override bool RegisterInternal() - { - if (!base.RegisterInternal()) - return false; - _registeredMelons.Add((T)this); - return true; - } - - protected private override void UnregisterInternal() - { - _registeredMelons.Remove((T)this); - } - - public static void ExecuteAll(LemonAction func, bool unregisterOnFail = false, string unregistrationReason = null) - => ExecuteList(func, _registeredMelons, unregisterOnFail, unregistrationReason); + System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // To make sure that the type initializer of T was triggered. } + + public sealed override string MelonTypeName => TypeName; + + private protected override bool RegisterInternal() + { + if (!base.RegisterInternal()) + return false; + _registeredMelons.Add((T)this); + return true; + } + + private protected override void UnregisterInternal() + { + _registeredMelons.Remove((T)this); + } + + public static void ExecuteAll(LemonAction func, bool unregisterOnFail = false, string unregistrationReason = null) + => ExecuteList(func, _registeredMelons, unregisterOnFail, unregistrationReason); } diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs index 7883038ba..fc4fe50ee 100644 --- a/MelonLoader/MelonUtils.cs +++ b/MelonLoader/MelonUtils.cs @@ -1,634 +1,622 @@ -using System; +using AssetsTools.NET; +using AssetsTools.NET.Extra; +using HarmonyLib; +using MelonLoader.InternalUtils; +using MelonLoader.Lemons.Cryptography; +using MelonLoader.TinyJSON; +using MelonLoader.Utils; +using MonoMod.Cil; +using MonoMod.Utils; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; -using MonoMod.Cil; -using MonoMod.Utils; -using HarmonyLib; -using MelonLoader.TinyJSON; -using MelonLoader.InternalUtils; -using AssetsTools.NET; -using AssetsTools.NET.Extra; -using MelonLoader.Lemons.Cryptography; -using MelonLoader.Utils; #pragma warning disable 0618 -namespace MelonLoader +namespace MelonLoader; + +public static class MelonUtils { - public static class MelonUtils + private static NativeLibrary.StringDelegate WineGetVersion; + //private static readonly Random RandomNumGen = new(); + private static readonly MethodInfo StackFrameGetMethod = typeof(StackFrame).GetMethod("GetMethod", BindingFlags.Instance | BindingFlags.Public); + private static readonly LemonSHA256 sha256 = new(); + private static readonly LemonSHA512 sha512 = new(); + + internal static void Setup(AppDomain domain) { - private static NativeLibrary.StringDelegate WineGetVersion; - //private static readonly Random RandomNumGen = new(); - private static readonly MethodInfo StackFrameGetMethod = typeof(StackFrame).GetMethod("GetMethod", BindingFlags.Instance | BindingFlags.Public); - private static readonly LemonSHA256 sha256 = new(); - private static readonly LemonSHA512 sha512 = new(); + using (var sha = SHA256.Create()) + HashCode = ComputeSimpleSHA256Hash(Assembly.GetExecutingAssembly().Location); - internal static void Setup(AppDomain domain) - { - using (var sha = SHA256.Create()) - HashCode = ComputeSimpleSHA256Hash(Assembly.GetExecutingAssembly().Location); + Core.WelcomeMessage(); - Core.WelcomeMessage(); + if (MelonEnvironment.IsMonoRuntime) + SetCurrentDomainBaseDirectory(MelonEnvironment.GameRootDirectory, domain); - if (MelonEnvironment.IsMonoRuntime) - SetCurrentDomainBaseDirectory(MelonEnvironment.GameRootDirectory, domain); + if (!Directory.Exists(MelonEnvironment.UserDataDirectory)) + Directory.CreateDirectory(MelonEnvironment.UserDataDirectory); - if (!Directory.Exists(MelonEnvironment.UserDataDirectory)) - Directory.CreateDirectory(MelonEnvironment.UserDataDirectory); + if (!Directory.Exists(MelonEnvironment.UserLibsDirectory)) + Directory.CreateDirectory(MelonEnvironment.UserLibsDirectory); + AddNativeDLLDirectory(MelonEnvironment.UserLibsDirectory); - if (!Directory.Exists(MelonEnvironment.UserLibsDirectory)) - Directory.CreateDirectory(MelonEnvironment.UserLibsDirectory); - AddNativeDLLDirectory(MelonEnvironment.UserLibsDirectory); + MelonHandler.Setup(); + UnityInformationHandler.Setup(); - MelonHandler.Setup(); - UnityInformationHandler.Setup(); + CurrentGameAttribute = new MelonGameAttribute(UnityInformationHandler.GameDeveloper, UnityInformationHandler.GameName); + CurrentPlatform = IsGame32Bit() ? MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X86 : MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64; + CurrentDomain = IsGameIl2Cpp() ? MelonPlatformDomainAttribute.CompatibleDomains.IL2CPP : MelonPlatformDomainAttribute.CompatibleDomains.MONO; + } - CurrentGameAttribute = new MelonGameAttribute(UnityInformationHandler.GameDeveloper, UnityInformationHandler.GameName); - CurrentPlatform = IsGame32Bit() ? MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X86 : MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64; - CurrentDomain = IsGameIl2Cpp() ? MelonPlatformDomainAttribute.CompatibleDomains.IL2CPP : MelonPlatformDomainAttribute.CompatibleDomains.MONO; - } + [Obsolete("Use MelonEnvironment.MelonBaseDirectory instead")] + public static string BaseDirectory => MelonEnvironment.MelonBaseDirectory; + [Obsolete("Use MelonEnvironment.GameRootDirectory instead")] + public static string GameDirectory => MelonEnvironment.GameRootDirectory; + [Obsolete("Use MelonEnvironment.MelonLoaderDirectory instead")] + public static string MelonLoaderDirectory => MelonEnvironment.MelonLoaderDirectory; + [Obsolete("Use MelonEnvironment.UserDataDirectory instead")] + public static string UserDataDirectory => MelonEnvironment.UserDataDirectory; + [Obsolete("Use MelonEnvironment.UserLibsDirectory instead")] + public static string UserLibsDirectory => MelonEnvironment.UserLibsDirectory; + public static MelonPlatformAttribute.CompatiblePlatforms CurrentPlatform { get; private set; } + public static MelonPlatformDomainAttribute.CompatibleDomains CurrentDomain { get; private set; } + public static MelonGameAttribute CurrentGameAttribute { get; private set; } + public static T Clamp(T value, T min, T max) where T : IComparable + { + if (value.CompareTo(min) < 0) + return min; + return value.CompareTo(max) > 0 ? max : value; + } + public static string HashCode { get; private set; } + + // public static int RandomInt() + // { + // lock (RandomNumGen) + // return RandomNumGen.Next(); + // } + // + // public static int RandomInt(int max) + // { + // lock (RandomNumGen) + // return RandomNumGen.Next(max); + // } + // + // public static int RandomInt(int min, int max) + // { + // lock (RandomNumGen) + // return RandomNumGen.Next(min, max); + // } + // + // public static double RandomDouble() + // { + // lock (RandomNumGen) + // return RandomNumGen.NextDouble(); + // } + // + // public static string RandomString(int length) + // { + // StringBuilder builder = new(); + // for (int i = 0; i < length; i++) + // builder.Append(Convert.ToChar(Convert.ToInt32(Math.Floor(25 * RandomDouble())) + 65)); + // return builder.ToString(); + // } + + public static PlatformID GetPlatform => Environment.OSVersion.Platform; + + public static bool IsUnix => GetPlatform is PlatformID.Unix; + public static bool IsWindows => GetPlatform is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows or PlatformID.WinCE; + public static bool IsMac => GetPlatform is PlatformID.MacOSX; + + public static void SetCurrentDomainBaseDirectory(string dirpath, AppDomain domain = null) + { + if (MelonEnvironment.IsDotnetRuntime) + return; - [Obsolete("Use MelonEnvironment.MelonBaseDirectory instead")] - public static string BaseDirectory => MelonEnvironment.MelonBaseDirectory; - [Obsolete("Use MelonEnvironment.GameRootDirectory instead")] - public static string GameDirectory => MelonEnvironment.GameRootDirectory; - [Obsolete("Use MelonEnvironment.MelonLoaderDirectory instead")] - public static string MelonLoaderDirectory => MelonEnvironment.MelonLoaderDirectory; - [Obsolete("Use MelonEnvironment.UserDataDirectory instead")] - public static string UserDataDirectory => MelonEnvironment.UserDataDirectory; - [Obsolete("Use MelonEnvironment.UserLibsDirectory instead")] - public static string UserLibsDirectory => MelonEnvironment.UserLibsDirectory; - public static MelonPlatformAttribute.CompatiblePlatforms CurrentPlatform { get; private set; } - public static MelonPlatformDomainAttribute.CompatibleDomains CurrentDomain { get; private set; } - public static MelonGameAttribute CurrentGameAttribute { get; private set; } - public static T Clamp(T value, T min, T max) where T : IComparable { if (value.CompareTo(min) < 0) return min; if (value.CompareTo(max) > 0) return max; return value; } - public static string HashCode { get; private set; } - - // public static int RandomInt() - // { - // lock (RandomNumGen) - // return RandomNumGen.Next(); - // } - // - // public static int RandomInt(int max) - // { - // lock (RandomNumGen) - // return RandomNumGen.Next(max); - // } - // - // public static int RandomInt(int min, int max) - // { - // lock (RandomNumGen) - // return RandomNumGen.Next(min, max); - // } - // - // public static double RandomDouble() - // { - // lock (RandomNumGen) - // return RandomNumGen.NextDouble(); - // } - // - // public static string RandomString(int length) - // { - // StringBuilder builder = new(); - // for (int i = 0; i < length; i++) - // builder.Append(Convert.ToChar(Convert.ToInt32(Math.Floor(25 * RandomDouble())) + 65)); - // return builder.ToString(); - // } - - public static PlatformID GetPlatform => Environment.OSVersion.Platform; - - public static bool IsUnix => GetPlatform is PlatformID.Unix; - public static bool IsWindows => GetPlatform is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows or PlatformID.WinCE; - public static bool IsMac => GetPlatform is PlatformID.MacOSX; - - public static void SetCurrentDomainBaseDirectory(string dirpath, AppDomain domain = null) + domain ??= AppDomain.CurrentDomain; + try { - if(MelonEnvironment.IsDotnetRuntime) - return; - - if (domain == null) - domain = AppDomain.CurrentDomain; - try - { - ((AppDomainSetup)typeof(AppDomain).GetProperty("SetupInformationNoCopy", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(domain, new object[0])) - .SetApplicationBase(dirpath); - } - catch (Exception ex) { MelonLogger.Warning($"AppDomainSetup.ApplicationBase Exception: {ex}"); } - Directory.SetCurrentDirectory(dirpath); + ((AppDomainSetup)typeof(AppDomain).GetProperty("SetupInformationNoCopy", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(domain, new object[0])) + .SetApplicationBase(dirpath); } - - public static MelonBase GetMelonFromStackTrace() + catch (Exception ex) { - StackTrace st = new(3, true); - return GetMelonFromStackTrace(st); + MelonLogger.Warning($"AppDomainSetup.ApplicationBase Exception: {ex}"); } + Directory.SetCurrentDirectory(dirpath); + } - public static MelonBase GetMelonFromStackTrace(StackTrace st, bool allFrames = false) - { - if (st.FrameCount <= 0) - return null; + public static MelonBase GetMelonFromStackTrace() + { + StackTrace st = new(3, true); + return GetMelonFromStackTrace(st); + } - if (allFrames) - { - foreach (StackFrame frame in st.GetFrames()) - { - MelonBase ret = CheckForMelonInFrame(frame); - if (ret != null) - return ret; - } - return null; + public static MelonBase GetMelonFromStackTrace(StackTrace st, bool allFrames = false) + { + if (st.FrameCount <= 0) + return null; + if (allFrames) + { + foreach (var frame in st.GetFrames()) + { + var ret = CheckForMelonInFrame(frame); + if (ret != null) + return ret; } + return null; - MelonBase output = CheckForMelonInFrame(st); - if (output == null) - output = CheckForMelonInFrame(st, 1); - if (output == null) - output = CheckForMelonInFrame(st, 2); - return output; } - private static MelonBase CheckForMelonInFrame(StackTrace st, int frame = 0) - { - StackFrame sf = st.GetFrame(frame); - if (sf == null) - return null; + var output = CheckForMelonInFrame(st); + output ??= CheckForMelonInFrame(st, 1); + output ??= CheckForMelonInFrame(st, 2); + return output; + } - return CheckForMelonInFrame(sf); - } + private static MelonBase CheckForMelonInFrame(StackTrace st, int frame = 0) + { + var sf = st.GetFrame(frame); + return sf == null ? null : CheckForMelonInFrame(sf); + } - private static MelonBase CheckForMelonInFrame(StackFrame sf) - //The JIT compiler on .NET 6 on Windows 10 (win11 is fine, somehow) really doesn't like us calling StackFrame.GetMethod here - //Rather than trying to work out why, I'm just going to call it via reflection. - => GetMelonFromAssembly(((MethodBase)StackFrameGetMethod.Invoke(sf, new object[0]))?.DeclaringType?.Assembly); + private static MelonBase CheckForMelonInFrame(StackFrame sf) + //The JIT compiler on .NET 6 on Windows 10 (win11 is fine, somehow) really doesn't like us calling StackFrame.GetMethod here + //Rather than trying to work out why, I'm just going to call it via reflection. + => GetMelonFromAssembly(((MethodBase)StackFrameGetMethod.Invoke(sf, new object[0]))?.DeclaringType?.Assembly); - private static MelonBase GetMelonFromAssembly(Assembly asm) - => asm == null ? null : MelonHandler.Plugins.Cast().FirstOrDefault(x => x.Assembly == asm) ?? MelonHandler.Mods.FirstOrDefault(x => x.Assembly == asm); + private static MelonBase GetMelonFromAssembly(Assembly asm) + => asm == null ? null : MelonHandler.Plugins.Cast().FirstOrDefault(x => x.Assembly == asm) ?? MelonHandler.Mods.FirstOrDefault(x => x.Assembly == asm); - public static string ComputeSimpleSHA256Hash(string filePath) - { - if (!File.Exists(filePath)) - return null; + public static string ComputeSimpleSHA256Hash(string filePath) + { + if (!File.Exists(filePath)) + return null; - byte[] byteHash = File.ReadAllBytes(filePath); - if (byteHash == null) - return null; + var byteHash = File.ReadAllBytes(filePath); + return byteHash == null ? null : sha256.ComputeHash(byteHash).ToString("X2"); + } - return sha256.ComputeHash(byteHash).ToString("X2"); - } + public static string ComputeSimpleSHA512Hash(string filePath) + { + if (!File.Exists(filePath)) + return null; - public static string ComputeSimpleSHA512Hash(string filePath) - { - if (!File.Exists(filePath)) - return null; + var byteHash = File.ReadAllBytes(filePath); + return byteHash == null ? null : sha512.ComputeHash(byteHash).ToString("X2"); + } - byte[] byteHash = File.ReadAllBytes(filePath); - if (byteHash == null) - return null; + public static string ToString(this byte[] data) + { + var result = new StringBuilder(); + for (var i = 0; i < data.Length; i++) + result.Append(data[i].ToString()); + return result.ToString(); + } - return sha512.ComputeHash(byteHash).ToString("X2"); - } + public static string ToString(this byte[] data, string format) + { + var result = new StringBuilder(); + for (var i = 0; i < data.Length; i++) + result.Append(data[i].ToString(format)); + return result.ToString(); + } - public static string ToString(this byte[] data) - { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < data.Length; i++) - result.Append(data[i].ToString()); - return result.ToString(); - } + public static string ToString(this byte[] data, IFormatProvider provider) + { + var result = new StringBuilder(); + for (var i = 0; i < data.Length; i++) + result.Append(data[i].ToString(provider)); + return result.ToString(); + } - public static string ToString(this byte[] data, string format) - { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < data.Length; i++) - result.Append(data[i].ToString(format)); - return result.ToString(); - } + public static string ToString(this byte[] data, string format, IFormatProvider provider) + { + var result = new StringBuilder(); + for (var i = 0; i < data.Length; i++) + result.Append(data[i].ToString(format, provider)); + return result.ToString(); + } - public static string ToString(this byte[] data, IFormatProvider provider) + public static T ParseJSONStringtoStruct(string jsonstr) + { + if (string.IsNullOrEmpty(jsonstr)) + return default; + Variant jsonarr; + try { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < data.Length; i++) - result.Append(data[i].ToString(provider)); - return result.ToString(); + jsonarr = JSON.Load(jsonstr); } - - public static string ToString(this byte[] data, string format, IFormatProvider provider) + catch (Exception ex) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < data.Length; i++) - result.Append(data[i].ToString(format, provider)); - return result.ToString(); + MelonLogger.Error($"Exception while Decoding JSON String to JSON Variant: {ex}"); + return default; } - - public static T ParseJSONStringtoStruct(string jsonstr) + if (jsonarr == null) + return default; + T returnobj = default; + try { - if (string.IsNullOrEmpty(jsonstr)) - return default; - Variant jsonarr; - try { jsonarr = JSON.Load(jsonstr); } - catch (Exception ex) - { - MelonLogger.Error($"Exception while Decoding JSON String to JSON Variant: {ex}"); - return default; - } - if (jsonarr == null) - return default; - T returnobj = default; - try { returnobj = jsonarr.Make(); } - catch (Exception ex) { MelonLogger.Error($"Exception while Converting JSON Variant to {typeof(T).Name}: {ex}"); } - return returnobj; + returnobj = jsonarr.Make(); } - - public static T PullAttributeFromAssembly(Assembly asm, bool inherit = false) where T : Attribute + catch (Exception ex) { - T[] attributetbl = PullAttributesFromAssembly(asm, inherit); - if ((attributetbl == null) || (attributetbl.Length <= 0)) - return null; - return attributetbl[0]; + MelonLogger.Error($"Exception while Converting JSON Variant to {typeof(T).Name}: {ex}"); } + return returnobj; + } - public static T[] PullAttributesFromAssembly(Assembly asm, bool inherit = false) where T : Attribute - { - Attribute[] att_tbl = Attribute.GetCustomAttributes(asm, inherit); + public static T PullAttributeFromAssembly(Assembly asm, bool inherit = false) where T : Attribute + { + var attributetbl = PullAttributesFromAssembly(asm, inherit); + return (attributetbl == null) || (attributetbl.Length <= 0) ? null : attributetbl[0]; + } - if ((att_tbl == null) || (att_tbl.Length <= 0)) - return null; + public static T[] PullAttributesFromAssembly(Assembly asm, bool inherit = false) where T : Attribute + { + var att_tbl = Attribute.GetCustomAttributes(asm, inherit); - Type requestedType = typeof(T); - string requestedAssemblyName = requestedType.Assembly.GetName().Name; - List output = new(); - foreach (Attribute att in att_tbl) - { - Type attType = att.GetType(); - string attAssemblyName = attType.Assembly.GetName().Name; - - if ((attType == requestedType) - || IsTypeEqualToFullName(attType, requestedType.FullName) - || ((attAssemblyName.Equals("MelonLoader") - || attAssemblyName.Equals("MelonLoader.ModHandler")) - && (requestedAssemblyName.Equals("MelonLoader") - || requestedAssemblyName.Equals("MelonLoader.ModHandler")) - && IsTypeEqualToName(attType, requestedType.Name))) - output.Add(att as T); - } + if ((att_tbl == null) || (att_tbl.Length <= 0)) + return null; - return output.ToArray(); + var requestedType = typeof(T); + var requestedAssemblyName = requestedType.Assembly.GetName().Name; + List output = []; + foreach (var att in att_tbl) + { + var attType = att.GetType(); + var attAssemblyName = attType.Assembly.GetName().Name; + + if ((attType == requestedType) + || IsTypeEqualToFullName(attType, requestedType.FullName) + || ((attAssemblyName.Equals("MelonLoader") + || attAssemblyName.Equals("MelonLoader.ModHandler")) + && (requestedAssemblyName.Equals("MelonLoader") + || requestedAssemblyName.Equals("MelonLoader.ModHandler")) + && IsTypeEqualToName(attType, requestedType.Name))) + output.Add(att as T); } - public static bool IsTypeEqualToName(Type type1, string type2) - => type1.Name == type2 || (type1 != typeof(object) && IsTypeEqualToName(type1.BaseType, type2)); + return output.ToArray(); + } + + public static bool IsTypeEqualToName(Type type1, string type2) + => type1.Name == type2 || (type1 != typeof(object) && IsTypeEqualToName(type1.BaseType, type2)); - public static bool IsTypeEqualToFullName(Type type1, string type2) - => type1.FullName == type2 || (type1 != typeof(object) && IsTypeEqualToFullName(type1.BaseType, type2)); + public static bool IsTypeEqualToFullName(Type type1, string type2) + => type1.FullName == type2 || (type1 != typeof(object) && IsTypeEqualToFullName(type1.BaseType, type2)); - public static string MakePlural(this string str, int amount) - => amount == 1 ? str : $"{str}s"; + public static string MakePlural(this string str, int amount) + => amount == 1 ? str : $"{str}s"; - public static IEnumerable GetValidTypes(this Assembly asm) - => GetValidTypes(asm, null); + public static IEnumerable GetValidTypes(this Assembly asm) + => GetValidTypes(asm, null); - public static IEnumerable GetValidTypes(this Assembly asm, LemonFunc predicate) + public static IEnumerable GetValidTypes(this Assembly asm, LemonFunc predicate) + { + var returnval = Enumerable.Empty(); + try { - IEnumerable returnval = Enumerable.Empty(); - try { returnval = asm.GetTypes().AsEnumerable(); } - catch (ReflectionTypeLoadException ex) - { - //MelonLogger.Error($"Failed to get all types in assembly {asm.FullName} due to: {ex.Message}", ex); - returnval = ex.Types; - } - //catch (Exception ex) - //{ - //MelonLogger.Error($"Failed to get all types in assembly {asm.FullName} due to: {ex.Message}", ex); - // returnval = null; - //} - return returnval.Where(x => (x != null) && (predicate == null || predicate(x))); + returnval = asm.GetTypes().AsEnumerable(); } + catch (ReflectionTypeLoadException ex) + { + //MelonLogger.Error($"Failed to get all types in assembly {asm.FullName} due to: {ex.Message}", ex); + returnval = ex.Types; + } + //catch (Exception ex) + //{ + //MelonLogger.Error($"Failed to get all types in assembly {asm.FullName} due to: {ex.Message}", ex); + // returnval = null; + //} + return returnval.Where(x => (x != null) && (predicate == null || predicate(x))); + } - public static Type GetValidType(this Assembly asm, string typeName) - => GetValidType(asm, typeName, null); + public static Type GetValidType(this Assembly asm, string typeName) + => GetValidType(asm, typeName, null); - public static Type GetValidType(this Assembly asm, string typeName, LemonFunc predicate) + public static Type GetValidType(this Assembly asm, string typeName, LemonFunc predicate) + { + Type x; + try { - Type x = null; - try { x = asm.GetType(typeName); } - catch //(Exception ex) - { - //MelonLogger.Error($"Failed to get type {typeName} from assembly {asm.FullName} due to: {ex.Message}", ex); - x = null; - } - if ((x != null) && (predicate == null || predicate(x))) - return x; - return null; + x = asm.GetType(typeName); } - - public static bool IsNotImplemented(this MethodBase methodBase) + catch //(Exception ex) { - if (methodBase == null) - throw new ArgumentNullException(nameof(methodBase)); + //MelonLogger.Error($"Failed to get type {typeName} from assembly {asm.FullName} due to: {ex.Message}", ex); + x = null; + } + return (x != null) && (predicate == null || predicate(x)) ? x : null; + } - DynamicMethodDefinition method = methodBase.ToNewDynamicMethodDefinition(); - ILContext ilcontext = new(method.Definition); - ILCursor ilcursor = new(ilcontext); + public static bool IsNotImplemented(this MethodBase methodBase) + { + if (methodBase == null) + throw new ArgumentNullException(nameof(methodBase)); - bool returnval = (ilcursor.Instrs.Count == 2) - && (ilcursor.Instrs[1].OpCode.Code == Mono.Cecil.Cil.Code.Throw); + var method = methodBase.ToNewDynamicMethodDefinition(); + ILContext ilcontext = new(method.Definition); + ILCursor ilcursor = new(ilcontext); - ilcontext.Dispose(); - method.Dispose(); - return returnval; - } + var returnval = (ilcursor.Instrs.Count == 2) + && (ilcursor.Instrs[1].OpCode.Code == Mono.Cecil.Cil.Code.Throw); - public static bool IsManagedDLL(string path) - { - if (Path.GetExtension(path).ToLower() != ".dll") - return false; + ilcontext.Dispose(); + method.Dispose(); + return returnval; + } - try - { - AssemblyName.GetAssemblyName(path); - return true; - } - catch (FileLoadException) - { - return true; - } - catch - { - return false; - } - } + public static bool IsManagedDLL(string path) + { + if (Path.GetExtension(path).ToLower() != ".dll") + return false; - public static HarmonyMethod ToNewHarmonyMethod(this MethodInfo methodInfo) + try { - if (methodInfo == null) - throw new ArgumentNullException(nameof(methodInfo)); - return new HarmonyMethod(methodInfo); + AssemblyName.GetAssemblyName(path); + return true; } - - public static DynamicMethodDefinition ToNewDynamicMethodDefinition(this MethodBase methodBase) + catch (FileLoadException) { - if (methodBase == null) - throw new ArgumentNullException(nameof(methodBase)); - return new DynamicMethodDefinition(methodBase); + return true; } - - private static FieldInfo AppDomainSetup_application_base; - public static void SetApplicationBase(this AppDomainSetup _this, string value) + catch { - if (AppDomainSetup_application_base == null) - AppDomainSetup_application_base = typeof(AppDomainSetup).GetField("application_base", BindingFlags.NonPublic | BindingFlags.Instance); - if (AppDomainSetup_application_base != null) - AppDomainSetup_application_base.SetValue(_this, value); + return false; } + } - private static FieldInfo HashAlgorithm_HashSizeValue; - public static void SetHashSizeValue(this HashAlgorithm _this, int value) - { - if (HashAlgorithm_HashSizeValue == null) - HashAlgorithm_HashSizeValue = typeof(HashAlgorithm).GetField("HashSizeValue", BindingFlags.Public | BindingFlags.Instance); - if (HashAlgorithm_HashSizeValue != null) - HashAlgorithm_HashSizeValue.SetValue(_this, value); - } + public static HarmonyMethod ToNewHarmonyMethod(this MethodInfo methodInfo) + { + return methodInfo == null ? throw new ArgumentNullException(nameof(methodInfo)) : new HarmonyMethod(methodInfo); + } + + public static DynamicMethodDefinition ToNewDynamicMethodDefinition(this MethodBase methodBase) + { + return methodBase == null ? throw new ArgumentNullException(nameof(methodBase)) : new DynamicMethodDefinition(methodBase); + } + + private static FieldInfo AppDomainSetup_application_base; + public static void SetApplicationBase(this AppDomainSetup _this, string value) + { + AppDomainSetup_application_base ??= typeof(AppDomainSetup).GetField("application_base", BindingFlags.NonPublic | BindingFlags.Instance); + AppDomainSetup_application_base?.SetValue(_this, value); + } + + private static FieldInfo HashAlgorithm_HashSizeValue; + public static void SetHashSizeValue(this HashAlgorithm _this, int value) + { + HashAlgorithm_HashSizeValue ??= typeof(HashAlgorithm).GetField("HashSizeValue", BindingFlags.Public | BindingFlags.Instance); + HashAlgorithm_HashSizeValue?.SetValue(_this, value); + } - // Modified Version of System.IO.Path.HasExtension from .NET Framework's mscorlib.dll - public static bool ContainsExtension(this string path) + // Modified Version of System.IO.Path.HasExtension from .NET Framework's mscorlib.dll + public static bool ContainsExtension(this string path) + { + if (path != null) { - if (path != null) + path.CheckInvalidPathChars(); + var num = path.Length; + while (--num >= 0) { - path.CheckInvalidPathChars(); - int num = path.Length; - while (--num >= 0) - { - char c = path[num]; - if (c == '.') - return num != path.Length - 1; - if (c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar || c == Path.VolumeSeparatorChar) - break; - } + var c = path[num]; + if (c == '.') + return num != path.Length - 1; + if (c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar || c == Path.VolumeSeparatorChar) + break; } - return false; - } - - // Modified Version of System.IO.Path.CheckInvalidPathChars from .NET Framework's mscorlib.dll - private static void CheckInvalidPathChars(this string path) - { - foreach (int num in path) - if (num == 34 || num == 60 || num == 62 || num == 124 || num < 32) - throw new ArgumentException("Argument_InvalidPathChars", nameof(path)); } + return false; + } - public static void GetDelegate(this IntPtr ptr, out T output) where T : Delegate - => output = GetDelegate(ptr); - public static T GetDelegate(this IntPtr ptr) where T : Delegate - => GetDelegate(ptr, typeof(T)) as T; - public static Delegate GetDelegate(this IntPtr ptr, Type type) - { - if (ptr == IntPtr.Zero) - throw new ArgumentNullException(nameof(ptr)); - Delegate del = Marshal.GetDelegateForFunctionPointer(ptr, type); - if (del == null) - throw new Exception($"Unable to Get Delegate of Type {type.FullName} for Function Pointer!"); - return del; - } - public static IntPtr GetFunctionPointer(this Delegate del) - => Marshal.GetFunctionPointerForDelegate(del); + // Modified Version of System.IO.Path.CheckInvalidPathChars from .NET Framework's mscorlib.dll + private static void CheckInvalidPathChars(this string path) + { + foreach (int num in path) + if (num is 34 or 60 or 62 or 124 or < 32) + throw new ArgumentException("Argument_InvalidPathChars", nameof(path)); + } - public static NativeLibrary ToNewNativeLibrary(this IntPtr ptr) - => new(ptr); - public static NativeLibrary ToNewNativeLibrary(this IntPtr ptr) - => new(ptr); - public static IntPtr GetNativeLibraryExport(this IntPtr ptr, string name) - => NativeLibrary.GetExport(ptr, name); + public static void GetDelegate(this IntPtr ptr, out T output) where T : Delegate + => output = GetDelegate(ptr); + public static T GetDelegate(this IntPtr ptr) where T : Delegate + => GetDelegate(ptr, typeof(T)) as T; + public static Delegate GetDelegate(this IntPtr ptr, Type type) + { + if (ptr == IntPtr.Zero) + throw new ArgumentNullException(nameof(ptr)); + var del = Marshal.GetDelegateForFunctionPointer(ptr, type); + return del == null ? throw new Exception($"Unable to Get Delegate of Type {type.FullName} for Function Pointer!") : del; + } + public static IntPtr GetFunctionPointer(this Delegate del) + => Marshal.GetFunctionPointerForDelegate(del); - public static ClassPackageFile LoadIncludedClassPackage(this AssetsManager assetsManager) - { - ClassPackageFile classPackage = null; - using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MelonLoader.Resources.classdata.tpk")) - classPackage = assetsManager.LoadClassPackage(stream); - return classPackage; - } + public static NativeLibrary ToNewNativeLibrary(this IntPtr ptr) + => new(ptr); + public static NativeLibrary ToNewNativeLibrary(this IntPtr ptr) + => new(ptr); + public static IntPtr GetNativeLibraryExport(this IntPtr ptr, string name) + => NativeLibrary.GetExport(ptr, name); - [Obsolete("MelonLoader.MelonUtils.GetUnityVersion() is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] - public static string GetUnityVersion() => UnityInformationHandler.EngineVersion.ToStringWithoutType(); - [Obsolete("MelonLoader.MelonUtils.GameDeveloper is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead.")] - public static string GameDeveloper { get => UnityInformationHandler.GameDeveloper; } - [Obsolete("MelonLoader.MelonUtils.GameName is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead.")] - public static string GameName { get => UnityInformationHandler.GameName; } - [Obsolete("MelonLoader.MelonUtils.GameVersion is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameVersion instead.")] - public static string GameVersion { get => UnityInformationHandler.GameVersion; } + public static ClassPackageFile LoadIncludedClassPackage(this AssetsManager assetsManager) + { + ClassPackageFile classPackage = null; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MelonLoader.Resources.classdata.tpk")) + classPackage = assetsManager.LoadClassPackage(stream); + return classPackage; + } + [Obsolete("MelonLoader.MelonUtils.GetUnityVersion() is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] + public static string GetUnityVersion() => UnityInformationHandler.EngineVersion.ToStringWithoutType(); + [Obsolete("MelonLoader.MelonUtils.GameDeveloper is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead.")] + public static string GameDeveloper { get => UnityInformationHandler.GameDeveloper; } + [Obsolete("MelonLoader.MelonUtils.GameName is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead.")] + public static string GameName { get => UnityInformationHandler.GameName; } + [Obsolete("MelonLoader.MelonUtils.GameVersion is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameVersion instead.")] + public static string GameVersion { get => UnityInformationHandler.GameVersion; } - public static unsafe bool IsGame32Bit() => + public static unsafe bool IsGame32Bit() => #if X64 - false; + false; #else - true; + true; #endif + public static bool IsGameIl2Cpp() => Directory.Exists(MelonEnvironment.Il2CppDataDirectory); - public static bool IsGameIl2Cpp() => Directory.Exists(MelonEnvironment.Il2CppDataDirectory); + public static bool IsOldMono() => File.Exists(MelonEnvironment.UnityGameDataDirectory + "\\Mono\\mono.dll") || + File.Exists(MelonEnvironment.UnityGameDataDirectory + "\\Mono\\libmono.so"); - public static bool IsOldMono() => File.Exists(MelonEnvironment.UnityGameDataDirectory + "\\Mono\\mono.dll") || - File.Exists(MelonEnvironment.UnityGameDataDirectory + "\\Mono\\libmono.so"); + public static bool IsUnderWineOrSteamProton() => WineGetVersion is not null; - public static bool IsUnderWineOrSteamProton() => WineGetVersion is not null; + [Obsolete("Use MelonEnvironment.GameExecutablePath instead")] + public static string GetApplicationPath() => MelonEnvironment.GameExecutablePath; - [Obsolete("Use MelonEnvironment.GameExecutablePath instead")] - public static string GetApplicationPath() => MelonEnvironment.GameExecutablePath; + [Obsolete("Use MelonEnvironment.UnityGameDataDirectory instead")] + public static string GetGameDataDirectory() => MelonEnvironment.UnityGameDataDirectory; - [Obsolete("Use MelonEnvironment.UnityGameDataDirectory instead")] - public static string GetGameDataDirectory() => MelonEnvironment.UnityGameDataDirectory; + [Obsolete("Use MelonEnvironment.MelonManagedDirectory instead")] + public static string GetManagedDirectory() => MelonEnvironment.MelonManagedDirectory; - [Obsolete("Use MelonEnvironment.MelonManagedDirectory instead")] - public static string GetManagedDirectory() => MelonEnvironment.MelonManagedDirectory; - - public static void SetConsoleTitle(string title) - { - if (!MelonLaunchOptions.Console.ShouldSetTitle || MelonLaunchOptions.Console.ShouldHide) - return; - - Console.Title = title; - } - - public static string GetFileProductName(string filepath) - { - var fileInfo = FileVersionInfo.GetVersionInfo(filepath); - if (fileInfo != null) - return fileInfo.ProductName; - return null; - } - - public static void AddNativeDLLDirectory(string path) - { - if (!IsWindows && !IsUnix) - return; - - path = Path.GetFullPath(path); - if (!Directory.Exists(path)) - return; - - string envName = IsWindows ? "PATH" : "LD_LIBRARY_PATH"; - string envSep = IsWindows ? ";" : ":"; - string envPaths = Environment.GetEnvironmentVariable(envName); - Environment.SetEnvironmentVariable(envName, $"{envPaths}{envSep}{path}"); - } + public static void SetConsoleTitle(string title) + { + if (!MelonLaunchOptions.Console.ShouldSetTitle || MelonLaunchOptions.Console.ShouldHide) + return; - internal static void SetupWineCheck() - { - if (IsUnix || IsMac) - return; + Console.Title = title; + } - IntPtr dll = NativeLibrary.LoadLib("ntdll.dll"); - if (dll == IntPtr.Zero) - return; + public static string GetFileProductName(string filepath) + { + var fileInfo = FileVersionInfo.GetVersionInfo(filepath); + return fileInfo != null ? fileInfo.ProductName : null; + } - IntPtr wine_get_version_proc = NativeLibrary.AgnosticGetProcAddress(dll, "wine_get_version"); - if (wine_get_version_proc == IntPtr.Zero) - return; + public static void AddNativeDLLDirectory(string path) + { + if (!IsWindows && !IsUnix) + return; - WineGetVersion = (NativeLibrary.StringDelegate)Marshal.GetDelegateForFunctionPointer( - wine_get_version_proc, - typeof(NativeLibrary.StringDelegate) - ); - } + path = Path.GetFullPath(path); + if (!Directory.Exists(path)) + return; - [DllImport("ntdll.dll", SetLastError = true)] - internal static extern uint RtlGetVersion(out OsVersionInfo versionInformation); // return type should be the NtStatus enum + var envName = IsWindows ? "PATH" : "LD_LIBRARY_PATH"; + var envSep = IsWindows ? ";" : ":"; + var envPaths = Environment.GetEnvironmentVariable(envName); + Environment.SetEnvironmentVariable(envName, $"{envPaths}{envSep}{path}"); + } - [StructLayout(LayoutKind.Sequential)] - internal struct OsVersionInfo - { - private readonly uint OsVersionInfoSize; + internal static void SetupWineCheck() + { + if (IsUnix || IsMac) + return; - internal readonly uint MajorVersion; - internal readonly uint MinorVersion; + var dll = NativeLibrary.LoadLib("ntdll.dll"); + if (dll == IntPtr.Zero) + return; - internal readonly uint BuildNumber; + var wine_get_version_proc = NativeLibrary.AgnosticGetProcAddress(dll, "wine_get_version"); + if (wine_get_version_proc == IntPtr.Zero) + return; - private readonly uint PlatformId; + WineGetVersion = (NativeLibrary.StringDelegate)Marshal.GetDelegateForFunctionPointer( + wine_get_version_proc, + typeof(NativeLibrary.StringDelegate) + ); + } - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - internal readonly string CSDVersion; - } + [DllImport("ntdll.dll", SetLastError = true)] + internal static extern uint RtlGetVersion(out OsVersionInfo versionInformation); // return type should be the NtStatus enum - internal static string GetOSVersion() - { - if (IsUnix || IsMac) - return Environment.OSVersion.VersionString; + [StructLayout(LayoutKind.Sequential)] + internal struct OsVersionInfo + { + private readonly uint OsVersionInfoSize; - if (IsUnderWineOrSteamProton()) - return $"Wine {WineGetVersion()}"; - RtlGetVersion(out OsVersionInfo versionInformation); - var minor = versionInformation.MinorVersion; - var build = versionInformation.BuildNumber; + internal readonly uint MajorVersion; + internal readonly uint MinorVersion; - string versionString = ""; + internal readonly uint BuildNumber; - switch (versionInformation.MajorVersion) - { - case 4: - versionString = "Windows 95/98/Me/NT"; - break; - case 5: - if (minor == 0) - versionString = "Windows 2000"; - if (minor == 1) - versionString = "Windows XP"; - if (minor == 2) - versionString = "Windows 2003"; - break; - case 6: - if (minor == 0) - versionString = "Windows Vista"; - if (minor == 1) - versionString = "Windows 7"; - if (minor == 2) - versionString = "Windows 8"; - if (minor == 3) - versionString = "Windows 8.1"; - break; - case 10: - if (build >= 22000) - versionString = "Windows 11"; - else - versionString = "Windows 10"; - break; - default: - versionString = "Unknown"; - break; - } + private readonly uint PlatformId; - return $"{versionString}"; - } + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + internal readonly string CSDVersion; + } - [Obsolete("Use NativeUtils.NativeHook instead")] - public static void NativeHookAttach(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookAttach(target, detour); + internal static string GetOSVersion() + { + if (IsUnix || IsMac) + return Environment.OSVersion.VersionString; + + if (IsUnderWineOrSteamProton()) + return $"Wine {WineGetVersion()}"; + RtlGetVersion(out var versionInformation); + var minor = versionInformation.MinorVersion; + var build = versionInformation.BuildNumber; + + var versionString = ""; + + switch (versionInformation.MajorVersion) + { + case 4: + versionString = "Windows 95/98/Me/NT"; + break; + case 5: + if (minor == 0) + versionString = "Windows 2000"; + if (minor == 1) + versionString = "Windows XP"; + if (minor == 2) + versionString = "Windows 2003"; + break; + case 6: + if (minor == 0) + versionString = "Windows Vista"; + if (minor == 1) + versionString = "Windows 7"; + if (minor == 2) + versionString = "Windows 8"; + if (minor == 3) + versionString = "Windows 8.1"; + break; + case 10: + versionString = build >= 22000 ? "Windows 11" : "Windows 10"; + break; + default: + versionString = "Unknown"; + break; + } + + return $"{versionString}"; + } - [Obsolete("Use NativeUtils.NativeHook instead")] - internal static void NativeHookAttachDirect(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookAttachDirect(target, detour); + [Obsolete("Use NativeUtils.NativeHook instead")] + public static void NativeHookAttach(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookAttach(target, detour); - [Obsolete("Use NativeUtils.NativeHook instead")] - public static void NativeHookDetach(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookDetach(target, detour); + [Obsolete("Use NativeUtils.NativeHook instead")] + internal static void NativeHookAttachDirect(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookAttachDirect(target, detour); + [Obsolete("Use NativeUtils.NativeHook instead")] + public static void NativeHookDetach(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookDetach(target, detour); - //Removing these as they're private so mods shouldn't need them - //Can potentially be redirected to MelonEnvironment if really needed. + //Removing these as they're private so mods shouldn't need them + //Can potentially be redirected to MelonEnvironment if really needed. - //[MethodImpl(MethodImplOptions.InternalCall)] - //[return: MarshalAs(UnmanagedType.LPStr)] - //private extern static string Internal_GetBaseDirectory(); - //[MethodImpl(MethodImplOptions.InternalCall)] - //[return: MarshalAs(UnmanagedType.LPStr)] - //private extern static string Internal_GetGameDirectory(); - } + //[MethodImpl(MethodImplOptions.InternalCall)] + //[return: MarshalAs(UnmanagedType.LPStr)] + //private extern static string Internal_GetBaseDirectory(); + //[MethodImpl(MethodImplOptions.InternalCall)] + //[return: MarshalAs(UnmanagedType.LPStr)] + //private extern static string Internal_GetGameDirectory(); } diff --git a/MelonLoader/Melons/MelonFolderHandler.cs b/MelonLoader/Melons/MelonFolderHandler.cs index 4ca883d60..685200644 100644 --- a/MelonLoader/Melons/MelonFolderHandler.cs +++ b/MelonLoader/Melons/MelonFolderHandler.cs @@ -3,216 +3,215 @@ using System.Drawing; using System.IO; -namespace MelonLoader.Melons +namespace MelonLoader.Melons; + +internal class MelonFolderHandler { - internal class MelonFolderHandler + private static bool firstSpacer = false; + + internal enum eScanType + { + UserLibs, + Plugins, + Mods, + } + + internal static void Scan(eScanType type, string path) { - private static bool firstSpacer = false; + // Get Full Directory Path + path = Path.GetFullPath(path); - internal enum eScanType + // Log Loading Message + LogStart(path, type); + + // Find Melon Folders + List userLibDirs = []; + List pluginDirs = []; + List modDirs = []; + FindMelonFolders(type, path, ref userLibDirs, ref pluginDirs, ref modDirs); + + // Load UserLib Assemblies + var hasWroteLine = false; + List userLibAssemblies = []; + MelonPreprocessor.LoadFolders(userLibDirs, type == eScanType.UserLibs, ref hasWroteLine, ref userLibAssemblies); + + if (type != eScanType.UserLibs) { - UserLibs, - Plugins, - Mods, + // Load Plugins + List pluginAssemblies = []; + MelonPreprocessor.LoadFolders(pluginDirs, true, ref hasWroteLine, ref pluginAssemblies); + var pluginsLoaded = + LoadMelons(pluginAssemblies); + + // Load Mods + List modsLoaded = null; + if (type == eScanType.Mods) + { + List modAssemblies = []; + MelonPreprocessor.LoadFolders(modDirs, true, ref hasWroteLine, ref modAssemblies); + modsLoaded = LoadMelons(modAssemblies); + } + + // Register and Sort Plugins + if (hasWroteLine) + MelonLogger.WriteSpacer(); + MelonBase.RegisterSorted(pluginsLoaded); + + // Register and Sort Mods + if (type == eScanType.Mods) + MelonBase.RegisterSorted(modsLoaded); } - internal static void Scan(eScanType type, string path) - { - // Get Full Directory Path - path = Path.GetFullPath(path); + // Log Count + var count = (type == eScanType.UserLibs) + ? userLibAssemblies.Count + : ((type == eScanType.Mods) ? MelonMod.RegisteredMelons.Count : MelonPlugin.RegisteredMelons.Count); + var typeName = (type == eScanType.UserLibs) + ? "UserLib".MakePlural(count) + : ((type == eScanType.Mods) ? MelonMod.TypeName.MakePlural(count) : MelonPlugin.TypeName.MakePlural(count)); - // Log Loading Message - LogStart(path, type); + if (hasWroteLine) + MelonLogger.WriteLine(Color.Magenta); + MelonLogger.Msg($"{count} {typeName} loaded."); + + // Final Spacer + if (firstSpacer || (type == eScanType.Mods)) + MelonLogger.WriteSpacer(); + firstSpacer = true; + } - // Find Melon Folders - List userLibDirs = new(); - List pluginDirs = new(); - List modDirs = new(); - FindMelonFolders(type, path, ref userLibDirs, ref pluginDirs, ref modDirs); + private static void LogStart(string path, eScanType type) + { + var typeName = Enum.GetName(typeof(eScanType), type); + var loadingMsg = $"Loading {typeName} from '{path}'..."; + MelonLogger.WriteSpacer(); + MelonLogger.Msg(loadingMsg); + } - // Load UserLib Assemblies - bool hasWroteLine = false; - List userLibAssemblies = new(); - MelonPreprocessor.LoadFolders(userLibDirs, (type == eScanType.UserLibs), ref hasWroteLine, ref userLibAssemblies); + private static List LoadMelons(List melonAssemblies) + where T : MelonTypeBase + { + // Load Melons from Assembly + foreach (var asm in melonAssemblies) + asm.LoadMelons(); - if (type != eScanType.UserLibs) + List loadedMelons = []; + foreach (var asm in melonAssemblies) + foreach (var m in asm.LoadedMelons) { - // Load Plugins - List pluginAssemblies = new(); - MelonPreprocessor.LoadFolders(pluginDirs, true, ref hasWroteLine, ref pluginAssemblies); - List pluginsLoaded = - LoadMelons(pluginAssemblies); - - // Load Mods - List modsLoaded = null; - if (type == eScanType.Mods) + // Validate Type + if (m is T t) { - List modAssemblies = new(); - MelonPreprocessor.LoadFolders(modDirs, true, ref hasWroteLine, ref modAssemblies); - modsLoaded = LoadMelons(modAssemblies); + loadedMelons.Add(t); + continue; } - // Register and Sort Plugins - if (hasWroteLine) - MelonLogger.WriteSpacer(); - MelonBase.RegisterSorted(pluginsLoaded); - - // Register and Sort Mods - if (type == eScanType.Mods) - MelonBase.RegisterSorted(modsLoaded); + // Log Failure + MelonLogger.Warning($"Failed to load Melon '{m.Info.Name}' from '{m.MelonAssembly.Location}': The given Melon is a {m.MelonTypeName} and cannot be loaded as a {MelonTypeBase.TypeName}. Make sure it's in the right folder."); } + return loadedMelons; + } - // Log Count - int count = (type == eScanType.UserLibs) - ? userLibAssemblies.Count - : ((type == eScanType.Mods) ? MelonMod.RegisteredMelons.Count : MelonPlugin.RegisteredMelons.Count); - string typeName = (type == eScanType.UserLibs) - ? "UserLib".MakePlural(count) - : ((type == eScanType.Mods) ? MelonMod.TypeName.MakePlural(count) : MelonPlugin.TypeName.MakePlural(count)); + private static void FindMelonFolders( + eScanType scanType, + string path, + ref List userLibDirectories, + ref List pluginDirectories, + ref List modDirectories) + { + // Validate Path + if (!Directory.Exists(path)) + return; - if (hasWroteLine) - MelonLogger.WriteLine(Color.Magenta); - MelonLogger.Msg($"{count} {typeName} loaded."); + // Scan Directories + ScanFolder(scanType, path, ref userLibDirectories, ref pluginDirectories, ref modDirectories); - // Final Spacer - if (firstSpacer || (type == eScanType.Mods)) - MelonLogger.WriteSpacer(); - firstSpacer = true; + if (scanType == eScanType.UserLibs) + userLibDirectories.Add(path); + else + { + if (scanType == eScanType.Mods) + modDirectories.Add(path); + else + pluginDirectories.Add(path); } - private static void LogStart(string path, eScanType type) + // Add Directories to Resolver + foreach (var directory in userLibDirectories) { - string typeName = Enum.GetName(typeof(eScanType), type); - var loadingMsg = $"Loading {typeName} from '{path}'..."; - MelonLogger.WriteSpacer(); - MelonLogger.Msg(loadingMsg); + MelonUtils.AddNativeDLLDirectory(directory); + Resolver.MelonAssemblyResolver.AddSearchDirectory(directory); } - private static List LoadMelons(List melonAssemblies) - where T : MelonTypeBase + if (scanType != eScanType.UserLibs) { - // Load Melons from Assembly - foreach (var asm in melonAssemblies) - asm.LoadMelons(); - - List loadedMelons = new(); - foreach (var asm in melonAssemblies) - foreach (var m in asm.LoadedMelons) - { - // Validate Type - if (m is T t) - { - loadedMelons.Add(t); - continue; - } - - // Log Failure - MelonLogger.Warning($"Failed to load Melon '{m.Info.Name}' from '{m.MelonAssembly.Location}': The given Melon is a {m.MelonTypeName} and cannot be loaded as a {MelonTypeBase.TypeName}. Make sure it's in the right folder."); - } - return loadedMelons; + foreach (var directory in pluginDirectories) + Resolver.MelonAssemblyResolver.AddSearchDirectory(directory); + foreach (var directory in modDirectories) + Resolver.MelonAssemblyResolver.AddSearchDirectory(directory); } + } - private static void FindMelonFolders( - eScanType scanType, - string path, - ref List userLibDirectories, - ref List pluginDirectories, - ref List modDirectories) + private static void ScanFolder(eScanType scanType, + string path, + ref List userLibDirectories, + ref List pluginDirectories, + ref List modDirectories) + { + // Get Directories + var directories = Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly); + if ((directories == null) + || (directories.Length <= 0)) + return; + + // Parse Directories + foreach (var dir in directories) { // Validate Path - if (!Directory.Exists(path)) - return; - - // Scan Directories - ScanFolder(scanType, path, ref userLibDirectories, ref pluginDirectories, ref modDirectories); + if (!Directory.Exists(dir)) + continue; - if (scanType == eScanType.UserLibs) - userLibDirectories.Add(path); - else - { - if (scanType == eScanType.Mods) - modDirectories.Add(path); - else - pluginDirectories.Add(path); - } - - // Add Directories to Resolver - foreach (string directory in userLibDirectories) - { - MelonUtils.AddNativeDLLDirectory(directory); - Resolver.MelonAssemblyResolver.AddSearchDirectory(directory); - } + // Validate Manifest + var manifestPath = Path.Combine(dir, "manifest.json"); + if (!File.Exists(manifestPath)) + continue; - if (scanType != eScanType.UserLibs) + // Check for Deeper UserLibs + var userLibsPath = Path.Combine(dir, "UserLibs"); + if (Directory.Exists(userLibsPath)) { - foreach (string directory in pluginDirectories) - Resolver.MelonAssemblyResolver.AddSearchDirectory(directory); - foreach (string directory in modDirectories) - Resolver.MelonAssemblyResolver.AddSearchDirectory(directory); + userLibDirectories.Add(userLibsPath); + ScanFolder(eScanType.UserLibs, userLibsPath, ref userLibDirectories, ref pluginDirectories, ref modDirectories); } - } - private static void ScanFolder(eScanType scanType, - string path, - ref List userLibDirectories, - ref List pluginDirectories, - ref List modDirectories) - { - // Get Directories - string[] directories = Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly); - if ((directories == null) - || (directories.Length <= 0)) - return; - - // Parse Directories - foreach (var dir in directories) + // Is UserLibs Scan? + if (scanType == eScanType.UserLibs) + userLibDirectories.Add(dir); // Add to Directories List + else { - // Validate Path - if (!Directory.Exists(dir)) - continue; - - // Validate Manifest - string manifestPath = Path.Combine(dir, "manifest.json"); - if (!File.Exists(manifestPath)) - continue; - - // Check for Deeper UserLibs - string userLibsPath = Path.Combine(dir, "UserLibs"); - if (Directory.Exists(userLibsPath)) + // Check for Deeper Melon Folder + var melonPath = Path.Combine(dir, "Plugins"); + if (Directory.Exists(melonPath)) { - userLibDirectories.Add(userLibsPath); - ScanFolder(eScanType.UserLibs, userLibsPath, ref userLibDirectories, ref pluginDirectories, ref modDirectories); + pluginDirectories.Add(melonPath); + ScanFolder(eScanType.Plugins, melonPath, ref userLibDirectories, ref pluginDirectories, ref modDirectories); } - // Is UserLibs Scan? - if (scanType == eScanType.UserLibs) - userLibDirectories.Add(dir); // Add to Directories List - else + if (scanType == eScanType.Mods) { - // Check for Deeper Melon Folder - string melonPath = Path.Combine(dir, "Plugins"); + melonPath = Path.Combine(dir, "Mods"); if (Directory.Exists(melonPath)) { - pluginDirectories.Add(melonPath); - ScanFolder(eScanType.Plugins, melonPath, ref userLibDirectories, ref pluginDirectories, ref modDirectories); - } - - if (scanType == eScanType.Mods) - { - melonPath = Path.Combine(dir, "Mods"); - if (Directory.Exists(melonPath)) - { - modDirectories.Add(melonPath); - ScanFolder(scanType, melonPath, ref userLibDirectories, ref pluginDirectories, ref modDirectories); - } + modDirectories.Add(melonPath); + ScanFolder(scanType, melonPath, ref userLibDirectories, ref pluginDirectories, ref modDirectories); } - - // Add to Directories List - if (scanType == eScanType.Mods) - modDirectories.Add(dir); - else - pluginDirectories.Add(dir); } + + // Add to Directories List + if (scanType == eScanType.Mods) + modDirectories.Add(dir); + else + pluginDirectories.Add(dir); } } } diff --git a/MelonLoader/Melons/MelonPreprocessor.cs b/MelonLoader/Melons/MelonPreprocessor.cs index 76228fc0f..a053bb10e 100644 --- a/MelonLoader/Melons/MelonPreprocessor.cs +++ b/MelonLoader/Melons/MelonPreprocessor.cs @@ -1,94 +1,93 @@ -using System; +using Mono.Cecil; +using System; using System.Collections.Generic; using System.Drawing; using System.IO; -using Mono.Cecil; -namespace MelonLoader.Melons +namespace MelonLoader.Melons; + +internal static class MelonPreprocessor { - internal static class MelonPreprocessor + internal static void LoadFolders(List directoryPaths, + bool isMelon, + ref bool hasWroteLine, + ref List melonAssemblies) { - internal static void LoadFolders(List directoryPaths, - bool isMelon, - ref bool hasWroteLine, - ref List melonAssemblies) - { - // Find All Assemblies - Dictionary foundAssemblies = new(); - foreach (string path in directoryPaths) - PreprocessFolder(path, ref foundAssemblies); + // Find All Assemblies + Dictionary foundAssemblies = []; + foreach (var path in directoryPaths) + PreprocessFolder(path, ref foundAssemblies); - // Load from File Paths - foreach (var foundFile in foundAssemblies) + // Load from File Paths + foreach (var foundFile in foundAssemblies) + { + // Log + if (!hasWroteLine) { - // Log - if (!hasWroteLine) - { - hasWroteLine = true; - MelonLogger.WriteLine(Color.Magenta); - } + hasWroteLine = true; + MelonLogger.WriteLine(Color.Magenta); + } - // Load Assembly - var asm = MelonAssembly.LoadMelonAssembly(foundFile.Value.Item2, false); - if (asm == null) - continue; + // Load Assembly + var asm = MelonAssembly.LoadMelonAssembly(foundFile.Value.Item2, false); + if (asm == null) + continue; - // Queue Assembly for Melon Parsing - if (isMelon) - melonAssemblies.Add(asm); - } + // Queue Assembly for Melon Parsing + if (isMelon) + melonAssemblies.Add(asm); } + } - private static void PreprocessFolder(string path, - ref Dictionary foundAssemblies) - { - // Validate Path - if (!Directory.Exists(path)) - return; + private static void PreprocessFolder(string path, + ref Dictionary foundAssemblies) + { + // Validate Path + if (!Directory.Exists(path)) + return; - // Get DLLs in Directory - var files = Directory.GetFiles(path, "*.dll", SearchOption.TopDirectoryOnly); - foreach (var f in files) - { - // Ignore Native DLLs - if (!MelonUtils.IsManagedDLL(f)) - continue; + // Get DLLs in Directory + var files = Directory.GetFiles(path, "*.dll", SearchOption.TopDirectoryOnly); + foreach (var f in files) + { + // Ignore Native DLLs + if (!MelonUtils.IsManagedDLL(f)) + continue; - // Load Definition using Cecil - AssemblyDefinition asmDef = LoadDefinition(f); - if (asmDef == null) - continue; + // Load Definition using Cecil + var asmDef = LoadDefinition(f); + if (asmDef == null) + continue; - // Pull Name and Version from AssemblyDefinitionName - string name = $"{asmDef.Name.Name}"; - Version version = new(asmDef.Name.Version.ToString()); + // Pull Name and Version from AssemblyDefinitionName + var name = $"{asmDef.Name.Name}"; + Version version = new(asmDef.Name.Version.ToString()); - // Dispose of Definition - asmDef.Dispose(); + // Dispose of Definition + asmDef.Dispose(); - // Check for Existing Version - if (foundAssemblies.TryGetValue(name, out (Version, string) existingVersion) - && (existingVersion.Item1 >= version)) - continue; + // Check for Existing Version + if (foundAssemblies.TryGetValue(name, out var existingVersion) + && (existingVersion.Item1 >= version)) + continue; - // Add File to List - foundAssemblies[name] = (version, f); - } + // Add File to List + foundAssemblies[name] = (version, f); } + } - private static AssemblyDefinition LoadDefinition(string path) - { - path = Path.GetFullPath(path); + private static AssemblyDefinition LoadDefinition(string path) + { + path = Path.GetFullPath(path); - try - { - return AssemblyDefinition.ReadAssembly(path); - } - catch (Exception ex) - { - MelonLogger.Error($"Failed to load AssemblyDefinition from '{path}':\n{ex}"); - return null; - } + try + { + return AssemblyDefinition.ReadAssembly(path); + } + catch (Exception ex) + { + MelonLogger.Error($"Failed to load AssemblyDefinition from '{path}':\n{ex}"); + return null; } } } diff --git a/MelonLoader/Modules/MelonModule.cs b/MelonLoader/Modules/MelonModule.cs index ee23a5f83..c4f016828 100644 --- a/MelonLoader/Modules/MelonModule.cs +++ b/MelonLoader/Modules/MelonModule.cs @@ -8,129 +8,125 @@ using System.Runtime.Loader; #endif -namespace MelonLoader.Modules +namespace MelonLoader.Modules; + +/// +/// A base for external MelonLoader modules. +/// +public abstract class MelonModule { - /// - /// A base for external MelonLoader modules. - /// - public abstract class MelonModule - { - private Type moduleType; + private Type moduleType; - public string Name { get; private set; } - public Assembly Assembly { get; private set; } - public Info ModuleInfo { get; private set; } - protected MelonLogger.Instance LoggerInstance { get; private set; } + public string Name { get; private set; } + public Assembly Assembly { get; private set; } + public Info ModuleInfo { get; private set; } + protected MelonLogger.Instance LoggerInstance { get; private set; } - protected MelonModule() { } + protected MelonModule() { } - public virtual void OnInitialize() { } + public virtual void OnInitialize() { } - internal static MelonModule Load(Info moduleInfo) + internal static MelonModule Load(Info moduleInfo) + { + if (!File.Exists(moduleInfo.fullPath)) { - if (!File.Exists(moduleInfo.fullPath)) - { - MelonDebug.Msg($"MelonModule '{moduleInfo.fullPath}' doesn't exist, ignoring."); - return null; - } + MelonDebug.Msg($"MelonModule '{moduleInfo.fullPath}' doesn't exist, ignoring."); + return null; + } - if (moduleInfo.shouldBeRemoved != null && moduleInfo.shouldBeRemoved()) + if (moduleInfo.shouldBeRemoved != null && moduleInfo.shouldBeRemoved()) + { + MelonDebug.Msg($"Removing MelonModule '{moduleInfo.fullPath}'..."); + try { - MelonDebug.Msg($"Removing MelonModule '{moduleInfo.fullPath}'..."); - try - { - File.Delete(moduleInfo.fullPath); - } - catch (Exception ex) - { - MelonLogger.Warning($"Failed to remove MelonModule '{moduleInfo.fullPath}':\n{ex}"); - } - return null; + File.Delete(moduleInfo.fullPath); } - - if (moduleInfo.shouldBeIgnored != null && moduleInfo.shouldBeIgnored()) + catch (Exception ex) { - MelonDebug.Msg($"Ignoring MelonModule '{moduleInfo.fullPath}'..."); - return null; + MelonLogger.Warning($"Failed to remove MelonModule '{moduleInfo.fullPath}':\n{ex}"); } + return null; + } - Assembly asm; - try - { + if (moduleInfo.shouldBeIgnored != null && moduleInfo.shouldBeIgnored()) + { + MelonDebug.Msg($"Ignoring MelonModule '{moduleInfo.fullPath}'..."); + return null; + } + + Assembly asm; + try + { #if NET6_0_OR_GREATER - asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(moduleInfo.fullPath); + asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(moduleInfo.fullPath); #else - asm = Assembly.LoadFrom(moduleInfo.fullPath); + asm = Assembly.LoadFrom(moduleInfo.fullPath); #endif - } - catch (Exception ex) - { - MelonLogger.Warning($"Failed to load Assembly of MelonModule '{moduleInfo.fullPath}':\n{ex}"); - return null; - } - - var name = asm.GetName().Name; + } + catch (Exception ex) + { + MelonLogger.Warning($"Failed to load Assembly of MelonModule '{moduleInfo.fullPath}':\n{ex}"); + return null; + } - var type = asm.GetTypes().FirstOrDefault(x => typeof(MelonModule).IsAssignableFrom(x)); - if (type == null) - { - MelonLogger.Warning($"Failed to load MelonModule '{moduleInfo.fullPath}': No type deriving from MelonModule found."); - return null; - } + var name = asm.GetName().Name; - MelonModule obj; - try - { - obj = (MelonModule)Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, null, null); - } - catch (Exception ex) - { - MelonLogger.Warning($"Failed to initialize MelonModule '{moduleInfo.fullPath}':\n{ex}"); - return null; - } + var type = asm.GetTypes().FirstOrDefault(x => typeof(MelonModule).IsAssignableFrom(x)); + if (type == null) + { + MelonLogger.Warning($"Failed to load MelonModule '{moduleInfo.fullPath}': No type deriving from MelonModule found."); + return null; + } - obj.moduleType = type; - obj.Name = name; - obj.Assembly = asm; - obj.ModuleInfo = moduleInfo; - obj.LoggerInstance = new MelonLogger.Instance(name, Color.Magenta); // Magenta cool :) + MelonModule obj; + try + { + obj = (MelonModule)Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, null, null); + } + catch (Exception ex) + { + MelonLogger.Warning($"Failed to initialize MelonModule '{moduleInfo.fullPath}':\n{ex}"); + return null; + } - try - { - obj.OnInitialize(); - } - catch (Exception ex) - { - obj.LoggerInstance.Error($"Local initialization failed:\n{ex}"); - return null; - } + obj.moduleType = type; + obj.Name = name; + obj.Assembly = asm; + obj.ModuleInfo = moduleInfo; + obj.LoggerInstance = new MelonLogger.Instance(name, Color.Magenta); // Magenta cool :) - return obj; + try + { + obj.OnInitialize(); } - - public object SendMessage(string name, params object[] arguments) + catch (Exception ex) { - var msg = moduleType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + obj.LoggerInstance.Error($"Local initialization failed:\n{ex}"); + return null; + } - if (msg == null) - return null; + return obj; + } - return msg.Invoke(msg.IsStatic ? null : this, arguments); - } + public object SendMessage(string name, params object[] arguments) + { + var msg = moduleType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - public class Info - { - public readonly string fullPath; - internal readonly Func shouldBeRemoved; - internal readonly Func shouldBeIgnored; - internal MelonModule moduleGC; + return msg == null ? null : msg.Invoke(msg.IsStatic ? null : this, arguments); + } - internal Info(string path, Func shouldBeIgnored = null, Func shouldBeRemoved = null) - { - fullPath = Path.GetFullPath(path); - this.shouldBeRemoved = shouldBeRemoved; - this.shouldBeIgnored = shouldBeIgnored; - } + public class Info + { + public readonly string fullPath; + internal readonly Func shouldBeRemoved; + internal readonly Func shouldBeIgnored; + internal MelonModule moduleGC; + + internal Info(string path, Func shouldBeIgnored = null, Func shouldBeRemoved = null) + { + fullPath = Path.GetFullPath(path); + this.shouldBeRemoved = shouldBeRemoved; + this.shouldBeIgnored = shouldBeIgnored; } } } diff --git a/MelonLoader/NativeLibrary.cs b/MelonLoader/NativeLibrary.cs index e0ca8f71d..96bb69418 100644 --- a/MelonLoader/NativeLibrary.cs +++ b/MelonLoader/NativeLibrary.cs @@ -1,137 +1,130 @@ using System; -using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace MelonLoader +namespace MelonLoader; + +public class NativeLibrary { - public class NativeLibrary + public readonly IntPtr Ptr; + public NativeLibrary(IntPtr ptr) { - public readonly IntPtr Ptr; - public NativeLibrary(IntPtr ptr) - { - if (ptr == IntPtr.Zero) - throw new ArgumentNullException(nameof(ptr)); - Ptr = ptr; - } - - public static NativeLibrary Load(string filepath) - => LoadLib(filepath).ToNewNativeLibrary(); - public static NativeLibrary Load(string filepath) - => LoadLib(filepath).ToNewNativeLibrary(); - public static T ReflectiveLoad(string filepath) - => Load(filepath).Instance; - public static IntPtr LoadLib(string filepath) - { - if (string.IsNullOrEmpty(filepath)) - throw new ArgumentNullException(nameof(filepath)); - IntPtr ptr = AgnosticLoadLibrary(filepath); - if (ptr == IntPtr.Zero) - throw new Exception($"Unable to Load Native Library {filepath}!"); - return ptr; - } + if (ptr == IntPtr.Zero) + throw new ArgumentNullException(nameof(ptr)); + Ptr = ptr; + } - public IntPtr GetExport(string name) - => GetExport(Ptr, name); - public Delegate GetExport(Type type, string name) - => GetExport(name).GetDelegate(type); - public T GetExport(string name) where T : Delegate - => GetExport(name).GetDelegate(); - public void GetExport(string name, out T output) where T : Delegate - => output = GetExport(name); - public static IntPtr GetExport(IntPtr nativeLib, string name) - { - if (nativeLib == IntPtr.Zero) - throw new ArgumentNullException(nameof(nativeLib)); - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException(nameof(name)); + public static NativeLibrary Load(string filepath) + => LoadLib(filepath).ToNewNativeLibrary(); + public static NativeLibrary Load(string filepath) + => LoadLib(filepath).ToNewNativeLibrary(); + public static T ReflectiveLoad(string filepath) + => Load(filepath).Instance; + public static IntPtr LoadLib(string filepath) + { + if (string.IsNullOrEmpty(filepath)) + throw new ArgumentNullException(nameof(filepath)); + var ptr = AgnosticLoadLibrary(filepath); + return ptr == IntPtr.Zero ? throw new Exception($"Unable to Load Native Library {filepath}!") : ptr; + } - IntPtr returnval = AgnosticGetProcAddress(nativeLib, name); - if (returnval == IntPtr.Zero) - throw new Exception($"Unable to Find Native Library Export {name}!"); + public IntPtr GetExport(string name) + => GetExport(Ptr, name); + public Delegate GetExport(Type type, string name) + => GetExport(name).GetDelegate(type); + public T GetExport(string name) where T : Delegate + => GetExport(name).GetDelegate(); + public void GetExport(string name, out T output) where T : Delegate + => output = GetExport(name); + public static IntPtr GetExport(IntPtr nativeLib, string name) + { + if (nativeLib == IntPtr.Zero) + throw new ArgumentNullException(nameof(nativeLib)); + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); - return returnval; - } + var returnval = AgnosticGetProcAddress(nativeLib, name); + return returnval == IntPtr.Zero ? throw new Exception($"Unable to Find Native Library Export {name}!") : returnval; + } - public static IntPtr AgnosticLoadLibrary(string name) - { + public static IntPtr AgnosticLoadLibrary(string name) + { #if WINDOWS - return LoadLibrary(name); + return LoadLibrary(name); #elif LINUX - if (!Path.HasExtension(name)) - name += ".so"; - - return dlopen(name, RTLD_NOW); + if (!Path.HasExtension(name)) + name += ".so"; + + return dlopen(name, RTLD_NOW); #endif - } + } - public static IntPtr AgnosticGetProcAddress(IntPtr hModule, string lpProcName) - { + public static IntPtr AgnosticGetProcAddress(IntPtr hModule, string lpProcName) + { #if WINDOWS - return GetProcAddress(hModule, lpProcName); + return GetProcAddress(hModule, lpProcName); #elif LINUX - return dlsym(hModule, lpProcName); + return dlsym(hModule, lpProcName); #endif - } - + } + #if WINDOWS - [DllImport("kernel32", CharSet = CharSet.Unicode)] - private static extern IntPtr LoadLibrary(string lpLibFileName); - [DllImport("kernel32")] - internal static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); - [DllImport("kernel32")] - internal static extern IntPtr FreeLibrary(IntPtr hModule); + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern IntPtr LoadLibrary(string lpLibFileName); + [DllImport("kernel32")] + internal static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); + [DllImport("kernel32")] + internal static extern IntPtr FreeLibrary(IntPtr hModule); #elif LINUX - [DllImport("libdl.so.2")] - protected static extern IntPtr dlopen(string filename, int flags); + [DllImport("libdl.so.2")] + protected static extern IntPtr dlopen(string filename, int flags); - [DllImport("libdl.so.2")] - protected static extern IntPtr dlsym(IntPtr handle, string symbol); + [DllImport("libdl.so.2")] + protected static extern IntPtr dlsym(IntPtr handle, string symbol); - const int RTLD_NOW = 2; // for dlopen's flags + const int RTLD_NOW = 2; // for dlopen's flags #endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.LPStr)] - internal delegate string StringDelegate(); - } - public class NativeLibrary : NativeLibrary - { - public readonly T Instance; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.LPStr)] + internal delegate string StringDelegate(); +} - public NativeLibrary(IntPtr ptr) : base(ptr) - { - if (ptr == IntPtr.Zero) - throw new ArgumentNullException(nameof(ptr)); +public class NativeLibrary : NativeLibrary +{ + public readonly T Instance; + + public NativeLibrary(IntPtr ptr) : base(ptr) + { + if (ptr == IntPtr.Zero) + throw new ArgumentNullException(nameof(ptr)); - Type specifiedType = typeof(T); - if (specifiedType.IsAbstract && specifiedType.IsSealed) - throw new Exception($"Specified Type {specifiedType.FullName} must be Non-Static!"); + var specifiedType = typeof(T); + if (specifiedType.IsAbstract && specifiedType.IsSealed) + throw new Exception($"Specified Type {specifiedType.FullName} must be Non-Static!"); - Instance = (T)Activator.CreateInstance(specifiedType); + Instance = (T)Activator.CreateInstance(specifiedType); - foreach (var fieldInfo in specifiedType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - { - if (fieldInfo.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0) - continue; + foreach (var fieldInfo in specifiedType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (fieldInfo.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0) + continue; - var fieldType = fieldInfo.FieldType; - if (fieldType.GetCustomAttributes(typeof(UnmanagedFunctionPointerAttribute), false).Length == 0) - continue; + var fieldType = fieldInfo.FieldType; + if (fieldType.GetCustomAttributes(typeof(UnmanagedFunctionPointerAttribute), false).Length == 0) + continue; - fieldInfo.SetValue(Instance, GetExport(fieldType, fieldInfo.Name)); - } + fieldInfo.SetValue(Instance, GetExport(fieldType, fieldInfo.Name)); + } - foreach (var propertyInfo in specifiedType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - { - var fieldType = propertyInfo.PropertyType; - if (fieldType.GetCustomAttributes(typeof(UnmanagedFunctionPointerAttribute), false).Length == 0) - continue; + foreach (var propertyInfo in specifiedType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + var fieldType = propertyInfo.PropertyType; + if (fieldType.GetCustomAttributes(typeof(UnmanagedFunctionPointerAttribute), false).Length == 0) + continue; - propertyInfo.SetValue(Instance, GetExport(fieldType, propertyInfo.Name), null); - } + propertyInfo.SetValue(Instance, GetExport(fieldType, propertyInfo.Name), null); } } } diff --git a/MelonLoader/NativeUtils/CppUtils.cs b/MelonLoader/NativeUtils/CppUtils.cs index bc9d978d0..efc98d1cb 100644 --- a/MelonLoader/NativeUtils/CppUtils.cs +++ b/MelonLoader/NativeUtils/CppUtils.cs @@ -2,138 +2,138 @@ using System.Collections.Generic; using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils +namespace MelonLoader.NativeUtils; + +public static class CppUtils { - public static class CppUtils + // Credits: https://stackoverflow.com/a/9995303 + private static int GetHexVal(char hex) { - // Credits: https://stackoverflow.com/a/9995303 - private static int GetHexVal(char hex) - { - int val = (int)hex; - return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); - } + var val = (int)hex; + return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); + } - [StructLayout(LayoutKind.Explicit)] - private class ConvertClass - { - [FieldOffset(0)] - public ulong valueULong; + [StructLayout(LayoutKind.Explicit)] + private class ConvertClass + { + [FieldOffset(0)] + public ulong valueULong; - [FieldOffset(0)] - public uint valueUInt; - } + [FieldOffset(0)] + public uint valueUInt; + } - public static unsafe IntPtr FunctionStart(IntPtr ptr) - { - long index = ptr.ToInt64(); - for (; *(byte*)index != 0x55 || *(long*)(index + 1) != 0xEC8B || *(byte*)(index + 4) != 0xEC; index--) ; - return (IntPtr)index; - } + public static unsafe IntPtr FunctionStart(IntPtr ptr) + { + var index = ptr.ToInt64(); + for (; *(byte*)index != 0x55 || *(long*)(index + 1) != 0xEC8B || *(byte*)(index + 4) != 0xEC; index--) + ; + return (IntPtr)index; + } - public static unsafe IntPtr ResolveRelativeInstruction(IntPtr instruction) - { - byte opcode = *(byte*)instruction; - if (opcode != 0xE8 && opcode != 0xE9) - return IntPtr.Zero; + public static unsafe IntPtr ResolveRelativeInstruction(IntPtr instruction) + { + var opcode = *(byte*)instruction; + if (opcode is not 0xE8 and not 0xE9) + return IntPtr.Zero; - return ResolvePtrOffset((IntPtr)((long)instruction + 1), (IntPtr)((long)instruction + 5)); // CALL: E8 [rel32] / JMP: E9 [rel32] - } + return ResolvePtrOffset((IntPtr)((long)instruction + 1), (IntPtr)((long)instruction + 5)); // CALL: E8 [rel32] / JMP: E9 [rel32] + } - public static unsafe IntPtr ResolvePtrOffset(IntPtr offset32Ptr, IntPtr nextInstructionPtr) - { - uint jmpOffset = *(uint*)offset32Ptr; - uint valueUInt = new ConvertClass() { valueULong = (ulong)nextInstructionPtr }.valueUInt; - long delta = nextInstructionPtr.ToInt64() - valueUInt; - uint newPtrInt = unchecked(valueUInt + jmpOffset); - return new IntPtr(newPtrInt + delta); - } + public static unsafe IntPtr ResolvePtrOffset(IntPtr offset32Ptr, IntPtr nextInstructionPtr) + { + var jmpOffset = *(uint*)offset32Ptr; + var valueUInt = new ConvertClass() { valueULong = (ulong)nextInstructionPtr }.valueUInt; + var delta = nextInstructionPtr.ToInt64() - valueUInt; + var newPtrInt = unchecked(valueUInt + jmpOffset); + return new IntPtr(newPtrInt + delta); + } - public static unsafe IntPtr[] SigscanAll(IntPtr module, int moduleSize, string signature) + public static unsafe IntPtr[] SigscanAll(IntPtr module, int moduleSize, string signature) + { + List ptrs = []; + var signatureSpaceless = signature.Replace(" ", ""); + var signatureLength = signatureSpaceless.Length / 2; + var signatureBytes = new byte[signatureLength]; + var signatureNullBytes = new bool[signatureLength]; + for (var i = 0; i < signatureLength; ++i) { - List ptrs = new List(); - string signatureSpaceless = signature.Replace(" ", ""); - int signatureLength = signatureSpaceless.Length / 2; - byte[] signatureBytes = new byte[signatureLength]; - bool[] signatureNullBytes = new bool[signatureLength]; - for (int i = 0; i < signatureLength; ++i) - { - if (signatureSpaceless[i * 2] == '?') - signatureNullBytes[i] = true; - else - signatureBytes[i] = (byte)((GetHexVal(signatureSpaceless[i * 2]) << 4) + (GetHexVal(signatureSpaceless[(i * 2) + 1]))); - } + if (signatureSpaceless[i * 2] == '?') + signatureNullBytes[i] = true; + else + signatureBytes[i] = (byte)((GetHexVal(signatureSpaceless[i * 2]) << 4) + GetHexVal(signatureSpaceless[(i * 2) + 1])); + } - long index = module.ToInt64(); - long maxIndex = index + moduleSize; - long tmpAddress = 0; - int processed = 0; + var index = module.ToInt64(); + var maxIndex = index + moduleSize; + long tmpAddress = 0; + var processed = 0; - while (index < maxIndex) + while (index < maxIndex) + { + if (signatureNullBytes[processed] || *(byte*)index == signatureBytes[processed]) { - if (signatureNullBytes[processed] || *(byte*)index == signatureBytes[processed]) - { - if (processed == 0) - tmpAddress = index; + if (processed == 0) + tmpAddress = index; - ++processed; + ++processed; - if (processed == signatureLength) - { - ptrs.Add((IntPtr)tmpAddress); - processed = 0; - } - } - else + if (processed == signatureLength) { + ptrs.Add((IntPtr)tmpAddress); processed = 0; } - - ++index; + } + else + { + processed = 0; } - return ptrs.ToArray(); + ++index; } - public static unsafe IntPtr Sigscan(IntPtr module, int moduleSize, string signature) + return ptrs.ToArray(); + } + + public static unsafe IntPtr Sigscan(IntPtr module, int moduleSize, string signature) + { + var signatureSpaceless = signature.Replace(" ", ""); + var signatureLength = signatureSpaceless.Length / 2; + var signatureBytes = new byte[signatureLength]; + var signatureNullBytes = new bool[signatureLength]; + for (var i = 0; i < signatureLength; ++i) { - string signatureSpaceless = signature.Replace(" ", ""); - int signatureLength = signatureSpaceless.Length / 2; - byte[] signatureBytes = new byte[signatureLength]; - bool[] signatureNullBytes = new bool[signatureLength]; - for (int i = 0; i < signatureLength; ++i) - { - if (signatureSpaceless[i * 2] == '?') - signatureNullBytes[i] = true; - else - signatureBytes[i] = (byte)((GetHexVal(signatureSpaceless[i * 2]) << 4) + (GetHexVal(signatureSpaceless[(i * 2) + 1]))); - } + if (signatureSpaceless[i * 2] == '?') + signatureNullBytes[i] = true; + else + signatureBytes[i] = (byte)((GetHexVal(signatureSpaceless[i * 2]) << 4) + GetHexVal(signatureSpaceless[(i * 2) + 1])); + } - long index = module.ToInt64(); - long maxIndex = index + moduleSize; - long tmpAddress = 0; - int processed = 0; + var index = module.ToInt64(); + var maxIndex = index + moduleSize; + long tmpAddress = 0; + var processed = 0; - while (index < maxIndex) + while (index < maxIndex) + { + if (signatureNullBytes[processed] || *(byte*)index == signatureBytes[processed]) { - if (signatureNullBytes[processed] || *(byte*)index == signatureBytes[processed]) - { - if (processed == 0) - tmpAddress = index; + if (processed == 0) + tmpAddress = index; - ++processed; - - if (processed == signatureLength) - return (IntPtr)tmpAddress; - } - else - { - processed = 0; - } + ++processed; - ++index; + if (processed == signatureLength) + return (IntPtr)tmpAddress; + } + else + { + processed = 0; } - return IntPtr.Zero; + ++index; } + + return IntPtr.Zero; } } diff --git a/MelonLoader/NativeUtils/NativeHooks.cs b/MelonLoader/NativeUtils/NativeHooks.cs index 42180b8a2..2e1ef0d47 100644 --- a/MelonLoader/NativeUtils/NativeHooks.cs +++ b/MelonLoader/NativeUtils/NativeHooks.cs @@ -1,139 +1,131 @@ using MelonLoader.InternalUtils; using System; -using System.CodeDom; using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; -namespace MelonLoader.NativeUtils +namespace MelonLoader.NativeUtils; + +public class NativeHook where T : Delegate { - public class NativeHook where T : Delegate + #region Private Values + private static readonly List _gcProtect = []; + + private IntPtr _targetHandle; + private IntPtr _detourHandle; + private IntPtr _trampolineHandle; + private T _trampoline; + #endregion + + #region Public Properties + public IntPtr Target { - #region Private Values - private static readonly List _gcProtect = new(); - - private IntPtr _targetHandle; - private IntPtr _detourHandle; - private IntPtr _trampolineHandle; - private T _trampoline; - #endregion - - #region Public Properties - public IntPtr Target + get + { + return _targetHandle; + } + + set { - get - { - return _targetHandle; - } - - set - { - if (value == IntPtr.Zero) - throw new ArgumentNullException("value"); - - _targetHandle = value; - } + if (value == IntPtr.Zero) + throw new ArgumentNullException("value"); + + _targetHandle = value; } + } - public IntPtr Detour + public IntPtr Detour + { + get { - get - { - return _detourHandle; - } - - set - { - if (value == IntPtr.Zero) - throw new ArgumentNullException("value"); - - _detourHandle = value; - } + return _detourHandle; } - public T Trampoline + set { - get => _trampoline; - private set - { - if (value == null) - throw new ArgumentNullException(nameof(value)); + if (value == IntPtr.Zero) + throw new ArgumentNullException("value"); - if (_trampoline != null) - _gcProtect.Remove(_trampoline); + _detourHandle = value; + } + } - _trampoline = value; - _gcProtect.Add(_trampoline); - } + public T Trampoline + { + get => _trampoline; + private set + { + if (_trampoline != null) + _gcProtect.Remove(_trampoline); + _trampoline = value ?? throw new ArgumentNullException(nameof(value)); + _gcProtect.Add(_trampoline); } - - public IntPtr TrampolineHandle + } + + public IntPtr TrampolineHandle + { + get => _trampolineHandle; + private set { - get => _trampolineHandle; - private set - { - if (value == IntPtr.Zero) - throw new ArgumentNullException(nameof(value)); - - _trampolineHandle = value; - } + if (value == IntPtr.Zero) + throw new ArgumentNullException(nameof(value)); + + _trampolineHandle = value; } + } - public bool IsHooked { get; private set; } - #endregion + public bool IsHooked { get; private set; } + #endregion - public NativeHook() { } + public NativeHook() { } - public NativeHook(IntPtr target, IntPtr detour) - { - if (target == IntPtr.Zero) - throw new ArgumentNullException("target"); + public NativeHook(IntPtr target, IntPtr detour) + { + if (target == IntPtr.Zero) + throw new ArgumentNullException("target"); - if (detour == IntPtr.Zero) - throw new ArgumentNullException("detour"); + if (detour == IntPtr.Zero) + throw new ArgumentNullException("detour"); - _targetHandle = target; - _detourHandle = detour; - } + _targetHandle = target; + _detourHandle = detour; + } - public unsafe void Attach() - { - if (IsHooked) - return; + public unsafe void Attach() + { + if (IsHooked) + return; - if (_targetHandle == IntPtr.Zero) - throw new NullReferenceException("The NativeHook's target has not been set!"); + if (_targetHandle == IntPtr.Zero) + throw new NullReferenceException("The NativeHook's target has not been set!"); - if (_detourHandle == IntPtr.Zero) - throw new NullReferenceException("The NativeHook's detour has not been set!"); + if (_detourHandle == IntPtr.Zero) + throw new NullReferenceException("The NativeHook's detour has not been set!"); - IntPtr trampoline = _targetHandle; - BootstrapInterop.NativeHookAttach((IntPtr)(&trampoline), _detourHandle); - _trampolineHandle = trampoline; - - _trampoline = (T)Marshal.GetDelegateForFunctionPointer(_trampolineHandle, typeof(T)); - _gcProtect.Add(_trampoline); + var trampoline = _targetHandle; + BootstrapInterop.NativeHookAttach((IntPtr)(&trampoline), _detourHandle); + _trampolineHandle = trampoline; - IsHooked = true; - } + _trampoline = (T)Marshal.GetDelegateForFunctionPointer(_trampolineHandle, typeof(T)); + _gcProtect.Add(_trampoline); - public unsafe void Detach() - { - if (!IsHooked) - return; + IsHooked = true; + } + + public unsafe void Detach() + { + if (!IsHooked) + return; - if (_targetHandle == IntPtr.Zero) - throw new NullReferenceException("The NativeHook's target has not been set!"); + if (_targetHandle == IntPtr.Zero) + throw new NullReferenceException("The NativeHook's target has not been set!"); - IntPtr original = _targetHandle; - BootstrapInterop.NativeHookDetach((IntPtr)(&original), _detourHandle); + var original = _targetHandle; + BootstrapInterop.NativeHookDetach((IntPtr)(&original), _detourHandle); - IsHooked= false; - _gcProtect.Remove(_trampoline); - _trampoline = null; - _trampolineHandle = IntPtr.Zero; - } + IsHooked = false; + _gcProtect.Remove(_trampoline); + _trampoline = null; + _trampolineHandle = IntPtr.Zero; } } diff --git a/MelonLoader/NativeUtils/PEParser/ImageDataDirectory.cs b/MelonLoader/NativeUtils/PEParser/ImageDataDirectory.cs index 7e56a4068..61031aff1 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageDataDirectory.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageDataDirectory.cs @@ -1,13 +1,12 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct ImageDataDirectory { - [StructLayout(LayoutKind.Explicit)] - public struct ImageDataDirectory - { - [FieldOffset(0)] - public uint virtualAddress; - [FieldOffset(4)] - public uint size; - } + [FieldOffset(0)] + public uint virtualAddress; + [FieldOffset(4)] + public uint size; } diff --git a/MelonLoader/NativeUtils/PEParser/ImageExportDirectory.cs b/MelonLoader/NativeUtils/PEParser/ImageExportDirectory.cs index 5bda97140..dc6e3b0ee 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageExportDirectory.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageExportDirectory.cs @@ -1,31 +1,30 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct ImageExportDirectory { - [StructLayout(LayoutKind.Explicit)] - public struct ImageExportDirectory - { - [FieldOffset(0)] - public uint characteristics; - [FieldOffset(4)] - public uint timedatestamp; - [FieldOffset(8)] - public ushort majorVersion; - [FieldOffset(10)] - public ushort minorVersion; - [FieldOffset(12)] - public uint name; - [FieldOffset(16)] - public uint base_; - [FieldOffset(20)] - public uint numberOfFunctions; - [FieldOffset(24)] - public uint numberOfNames; - [FieldOffset(28)] - public uint addressOfFunctions; - [FieldOffset(32)] - public uint addressOfNames; - [FieldOffset(36)] - public uint addressOfNameOrdinals; - } + [FieldOffset(0)] + public uint characteristics; + [FieldOffset(4)] + public uint timedatestamp; + [FieldOffset(8)] + public ushort majorVersion; + [FieldOffset(10)] + public ushort minorVersion; + [FieldOffset(12)] + public uint name; + [FieldOffset(16)] + public uint base_; + [FieldOffset(20)] + public uint numberOfFunctions; + [FieldOffset(24)] + public uint numberOfNames; + [FieldOffset(28)] + public uint addressOfFunctions; + [FieldOffset(32)] + public uint addressOfNames; + [FieldOffset(36)] + public uint addressOfNameOrdinals; } diff --git a/MelonLoader/NativeUtils/PEParser/ImageFileHeader.cs b/MelonLoader/NativeUtils/PEParser/ImageFileHeader.cs index c30b0ad98..9bf33737d 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageFileHeader.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageFileHeader.cs @@ -1,13 +1,12 @@ -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +public struct ImageFileHeader { - public struct ImageFileHeader - { - public ushort machine; - public ushort numberOfSections; - public uint timeDateStamp; - public uint pointerToSymbolTable; - public uint numberOfSymbols; - public ushort sizeOfOptionalHeader; - public ushort characteristrics; - } + public ushort machine; + public ushort numberOfSections; + public uint timeDateStamp; + public uint pointerToSymbolTable; + public uint numberOfSymbols; + public ushort sizeOfOptionalHeader; + public ushort characteristrics; } \ No newline at end of file diff --git a/MelonLoader/NativeUtils/PEParser/ImageNtHeaders.cs b/MelonLoader/NativeUtils/PEParser/ImageNtHeaders.cs index d255471bc..cc6c4efab 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageNtHeaders.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageNtHeaders.cs @@ -1,17 +1,16 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct ImageNtHeaders { - [StructLayout(LayoutKind.Explicit)] - public struct ImageNtHeaders - { - [FieldOffset(0)] - public uint signature; - [FieldOffset(4)] - public ImageFileHeader fileHeader; - [FieldOffset(24)] - public OptionalFileHeader64 optionalHeader64; - [FieldOffset(24)] - public OptionalFileHeader32 optionalHeader32; - } + [FieldOffset(0)] + public uint signature; + [FieldOffset(4)] + public ImageFileHeader fileHeader; + [FieldOffset(24)] + public OptionalFileHeader64 optionalHeader64; + [FieldOffset(24)] + public OptionalFileHeader32 optionalHeader32; } diff --git a/MelonLoader/NativeUtils/PEParser/ImageResourceDirectory.cs b/MelonLoader/NativeUtils/PEParser/ImageResourceDirectory.cs index 5ba54fc38..b31fa59a0 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageResourceDirectory.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageResourceDirectory.cs @@ -1,21 +1,20 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct ImageResourceDirectory { - [StructLayout(LayoutKind.Explicit)] - public struct ImageResourceDirectory - { - [FieldOffset(0)] - public uint characteristics; - [FieldOffset(4)] - public uint timedatestamp; - [FieldOffset(8)] - public ushort majorVersion; - [FieldOffset(10)] - public ushort minorVersion; - [FieldOffset(12)] - public uint numberOfNames; - [FieldOffset(14)] - public uint numberOfIds; - } + [FieldOffset(0)] + public uint characteristics; + [FieldOffset(4)] + public uint timedatestamp; + [FieldOffset(8)] + public ushort majorVersion; + [FieldOffset(10)] + public ushort minorVersion; + [FieldOffset(12)] + public uint numberOfNames; + [FieldOffset(14)] + public uint numberOfIds; } diff --git a/MelonLoader/NativeUtils/PEParser/ImageSectionHeader.cs b/MelonLoader/NativeUtils/PEParser/ImageSectionHeader.cs index 32c16fc21..f52860cae 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageSectionHeader.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageSectionHeader.cs @@ -1,30 +1,29 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct ImageSectionHeader { - [StructLayout(LayoutKind.Explicit)] - public struct ImageSectionHeader - { - //[FieldOffset(0)] - //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - //public char[8] name; - [FieldOffset(8)] - public int virtualSize; - [FieldOffset(12)] - public int virtualAddress; - [FieldOffset(16)] - public int sizeOfRawData; - [FieldOffset(20)] - public int pointerToRawData; - [FieldOffset(24)] - public int pointerToRelocations; - [FieldOffset(28)] - public int pointerToLinenumbers; - [FieldOffset(32)] - public ushort numberOfRelocations; - [FieldOffset(34)] - public ushort numberOfLinenumbers; - [FieldOffset(36)] - public /* DataSectionFlags */ uint characteristics; - } + //[FieldOffset(0)] + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + //public char[8] name; + [FieldOffset(8)] + public int virtualSize; + [FieldOffset(12)] + public int virtualAddress; + [FieldOffset(16)] + public int sizeOfRawData; + [FieldOffset(20)] + public int pointerToRawData; + [FieldOffset(24)] + public int pointerToRelocations; + [FieldOffset(28)] + public int pointerToLinenumbers; + [FieldOffset(32)] + public ushort numberOfRelocations; + [FieldOffset(34)] + public ushort numberOfLinenumbers; + [FieldOffset(36)] + public /* DataSectionFlags */ uint characteristics; } diff --git a/MelonLoader/NativeUtils/PEParser/ImageThunkData32.cs b/MelonLoader/NativeUtils/PEParser/ImageThunkData32.cs index 113cc1039..ada1b9898 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageThunkData32.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageThunkData32.cs @@ -1,17 +1,16 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct ImageThunkData32 { - [StructLayout(LayoutKind.Explicit)] - public struct ImageThunkData32 - { - [FieldOffset(0)] - public uint forwarderString; - [FieldOffset(0)] - public uint function; - [FieldOffset(0)] - public uint ordinal; - [FieldOffset(0)] - public uint addressOfData; - } + [FieldOffset(0)] + public uint forwarderString; + [FieldOffset(0)] + public uint function; + [FieldOffset(0)] + public uint ordinal; + [FieldOffset(0)] + public uint addressOfData; } diff --git a/MelonLoader/NativeUtils/PEParser/ImageThunkData64.cs b/MelonLoader/NativeUtils/PEParser/ImageThunkData64.cs index eb2e354a6..213dee49e 100644 --- a/MelonLoader/NativeUtils/PEParser/ImageThunkData64.cs +++ b/MelonLoader/NativeUtils/PEParser/ImageThunkData64.cs @@ -1,17 +1,16 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct ImageThunkData64 { - [StructLayout(LayoutKind.Explicit)] - public struct ImageThunkData64 - { - [FieldOffset(0)] - public ulong forwarderString; - [FieldOffset(0)] - public ulong function; - [FieldOffset(0)] - public ulong ordinal; - [FieldOffset(0)] - public ulong addressOfData; - } + [FieldOffset(0)] + public ulong forwarderString; + [FieldOffset(0)] + public ulong function; + [FieldOffset(0)] + public ulong ordinal; + [FieldOffset(0)] + public ulong addressOfData; } diff --git a/MelonLoader/NativeUtils/PEParser/OptionalFileHeader32.cs b/MelonLoader/NativeUtils/PEParser/OptionalFileHeader32.cs index f52a98750..61a53cc63 100644 --- a/MelonLoader/NativeUtils/PEParser/OptionalFileHeader32.cs +++ b/MelonLoader/NativeUtils/PEParser/OptionalFileHeader32.cs @@ -1,13 +1,12 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct OptionalFileHeader32 { - [StructLayout(LayoutKind.Explicit)] - public struct OptionalFileHeader32 - { - [FieldOffset(96)] - public ImageDataDirectory exportTable; - [FieldOffset(112)] - public ImageDataDirectory resourceTable; - } + [FieldOffset(96)] + public ImageDataDirectory exportTable; + [FieldOffset(112)] + public ImageDataDirectory resourceTable; } \ No newline at end of file diff --git a/MelonLoader/NativeUtils/PEParser/OptionalFileHeader64.cs b/MelonLoader/NativeUtils/PEParser/OptionalFileHeader64.cs index 97c355c03..5d48bb537 100644 --- a/MelonLoader/NativeUtils/PEParser/OptionalFileHeader64.cs +++ b/MelonLoader/NativeUtils/PEParser/OptionalFileHeader64.cs @@ -1,13 +1,12 @@ using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +[StructLayout(LayoutKind.Explicit)] +public struct OptionalFileHeader64 { - [StructLayout(LayoutKind.Explicit)] - public struct OptionalFileHeader64 - { - [FieldOffset(112)] - public ImageDataDirectory exportTable; - [FieldOffset(128)] - public ImageDataDirectory resourceTable; - } + [FieldOffset(112)] + public ImageDataDirectory exportTable; + [FieldOffset(128)] + public ImageDataDirectory resourceTable; } \ No newline at end of file diff --git a/MelonLoader/NativeUtils/PEParser/PEUtils.cs b/MelonLoader/NativeUtils/PEParser/PEUtils.cs index f4e55988e..01d79f3d2 100644 --- a/MelonLoader/NativeUtils/PEParser/PEUtils.cs +++ b/MelonLoader/NativeUtils/PEParser/PEUtils.cs @@ -2,95 +2,91 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace MelonLoader.NativeUtils.PEParser +namespace MelonLoader.NativeUtils.PEParser; + +public static class PEUtils { - public static class PEUtils + public static unsafe ImageNtHeaders* AnalyseModuleWin(IntPtr moduleBaseAddress) { - public static unsafe ImageNtHeaders* AnalyseModuleWin(IntPtr moduleBaseAddress) - { - //MelonLoader.MelonLogger.Msg("[moduleBaseAddress+0x0]: " + (*(byte*)(moduleBaseAddress + 0x0))); - if (*(byte*)((long)moduleBaseAddress + 0x0) != 0x4D || *(byte*)((long)moduleBaseAddress + 0x1) != 0x5A) - throw new ArgumentException("The passed module isn't a valid PE file"); + //MelonLoader.MelonLogger.Msg("[moduleBaseAddress+0x0]: " + (*(byte*)(moduleBaseAddress + 0x0))); + if (*(byte*)((long)moduleBaseAddress + 0x0) != 0x4D || *(byte*)((long)moduleBaseAddress + 0x1) != 0x5A) + throw new ArgumentException("The passed module isn't a valid PE file"); - int OFFSET_TO_PE_HEADER_OFFSET = 0x3c; - uint offsetToPESig = *(uint*)((long)moduleBaseAddress + OFFSET_TO_PE_HEADER_OFFSET); - IntPtr pPESig = new IntPtr((long)moduleBaseAddress + offsetToPESig); + var OFFSET_TO_PE_HEADER_OFFSET = 0x3c; + var offsetToPESig = *(uint*)((long)moduleBaseAddress + OFFSET_TO_PE_HEADER_OFFSET); + var pPESig = new IntPtr((long)moduleBaseAddress + offsetToPESig); + return *(byte*)((long)pPESig + 0x0) != 0x50 || *(byte*)((long)pPESig + 0x1) != 0x45 || *(byte*)((long)pPESig + 0x2) != 0x0 || *(byte*)((long)pPESig + 0x3) != 0x0 + ? throw new ArgumentException("The passed module isn't a valid PE file") + : (ImageNtHeaders*)pPESig; + } - if (*(byte*)((long)pPESig + 0x0) != 0x50 || *(byte*)((long)pPESig + 0x1) != 0x45 || *(byte*)((long)pPESig + 0x2) != 0x0 || *(byte*)((long)pPESig + 0x3) != 0x0) - throw new ArgumentException("The passed module isn't a valid PE file"); - - return (ImageNtHeaders*)pPESig; - } + public static IntPtr GetExportedFunctionPointerForModule(string moduleName, string importName) + { + var moduleAddress = IntPtr.Zero; - public static IntPtr GetExportedFunctionPointerForModule(string moduleName, string importName) + foreach (ProcessModule module in Process.GetCurrentProcess().Modules) { - IntPtr moduleAddress = IntPtr.Zero; - - foreach (ProcessModule module in Process.GetCurrentProcess().Modules) + if (module.ModuleName == moduleName) { - if (module.ModuleName == moduleName) - { - moduleAddress = module.BaseAddress; - break; - } - } - - if (moduleAddress == IntPtr.Zero) - { - MelonLogger.Error($"Failed to find module \"{moduleName}\""); - return IntPtr.Zero; + moduleAddress = module.BaseAddress; + break; } - - return GetExportedFunctionPointerForModule((long)moduleAddress, importName); } - public static unsafe IntPtr GetExportedFunctionPointerForModule(long moduleBaseAddress, string importName) + if (moduleAddress == IntPtr.Zero) { - ImageNtHeaders* imageNtHeaders = AnalyseModuleWin((IntPtr)moduleBaseAddress); - ImageSectionHeader* pSech = ImageFirstSection(imageNtHeaders); - ImageDataDirectory* imageDirectoryEntryExport = MelonUtils.IsGame32Bit() ? &imageNtHeaders->optionalHeader32.exportTable : &imageNtHeaders->optionalHeader64.exportTable; - - ImageExportDirectory* pExportDirectory = (ImageExportDirectory*)((long)moduleBaseAddress + imageDirectoryEntryExport->virtualAddress); - //MelonLoader.MelonLogger.Msg("pExportDirectory at " + string.Format("{0:X}", (ulong)pExportDirectory - (ulong)moduleBaseAddress)); - for (uint i = 0; i < imageDirectoryEntryExport->size / sizeof(ImageExportDirectory); ++i) - { - ImageExportDirectory* pExportDirectoryI = pExportDirectory + i; - //MelonLoader.MelonLogger.Msg("pExportDirectoryI->name: " + string.Format("{0:X}", pExportDirectoryI->name)); - if (pExportDirectoryI->name != 0) - { - string imagename = Marshal.PtrToStringAnsi((IntPtr)((long)moduleBaseAddress + pExportDirectoryI->name)); - //string imagename = CppUtils.CharArrayPtrToString((IntPtr)pExportDirectoryI->name); - //MelonLoader.MelonLogger.Msg("imagename: " + imagename); - /* - if (imagename != "UnityPlayer.dll") - continue; - */ + MelonLogger.Error($"Failed to find module \"{moduleName}\""); + return IntPtr.Zero; + } + return GetExportedFunctionPointerForModule((long)moduleAddress, importName); + } - long baseNameOrdinalOffset = moduleBaseAddress + (int)pExportDirectoryI->addressOfNameOrdinals; - long baseFunctionOffset = moduleBaseAddress + (int)pExportDirectoryI->addressOfFunctions; - long baseNameOffset = moduleBaseAddress + (int)pExportDirectoryI->addressOfNames; + public static unsafe IntPtr GetExportedFunctionPointerForModule(long moduleBaseAddress, string importName) + { + var imageNtHeaders = AnalyseModuleWin((IntPtr)moduleBaseAddress); + var pSech = ImageFirstSection(imageNtHeaders); + var imageDirectoryEntryExport = MelonUtils.IsGame32Bit() ? &imageNtHeaders->optionalHeader32.exportTable : &imageNtHeaders->optionalHeader64.exportTable; - for (int j = 0; j < pExportDirectoryI->numberOfNames; ++j) - { - ushort ordinal = *(ushort*)((long)baseNameOrdinalOffset + j * 2); - long functionnameAddress = moduleBaseAddress + *(int*)(baseNameOffset + j * 4); - long functionaddress = moduleBaseAddress + *(int*)(baseFunctionOffset + ordinal * 4); - string importname = Marshal.PtrToStringAnsi((IntPtr)functionnameAddress); - //MelonLoader.MelonLogger.Msg($"{imagename}::{importname} @ 0x{((ulong)functionaddress - (ulong)moduleBaseAddress):X} (0x{functionaddress:X} - 0x{moduleBaseAddress:X})"); - if (importname == importName) - return (IntPtr)functionaddress; - } + var pExportDirectory = (ImageExportDirectory*)(moduleBaseAddress + imageDirectoryEntryExport->virtualAddress); + //MelonLoader.MelonLogger.Msg("pExportDirectory at " + string.Format("{0:X}", (ulong)pExportDirectory - (ulong)moduleBaseAddress)); + for (uint i = 0; i < imageDirectoryEntryExport->size / sizeof(ImageExportDirectory); ++i) + { + var pExportDirectoryI = pExportDirectory + i; + //MelonLoader.MelonLogger.Msg("pExportDirectoryI->name: " + string.Format("{0:X}", pExportDirectoryI->name)); + if (pExportDirectoryI->name != 0) + { + var imagename = Marshal.PtrToStringAnsi((IntPtr)(moduleBaseAddress + pExportDirectoryI->name)); + //string imagename = CppUtils.CharArrayPtrToString((IntPtr)pExportDirectoryI->name); + //MelonLoader.MelonLogger.Msg("imagename: " + imagename); + /* + if (imagename != "UnityPlayer.dll") + continue; + */ + + var baseNameOrdinalOffset = moduleBaseAddress + (int)pExportDirectoryI->addressOfNameOrdinals; + var baseFunctionOffset = moduleBaseAddress + (int)pExportDirectoryI->addressOfFunctions; + var baseNameOffset = moduleBaseAddress + (int)pExportDirectoryI->addressOfNames; + + for (var j = 0; j < pExportDirectoryI->numberOfNames; ++j) + { + var ordinal = *(ushort*)(baseNameOrdinalOffset + (j * 2)); + var functionnameAddress = moduleBaseAddress + *(int*)(baseNameOffset + (j * 4)); + var functionaddress = moduleBaseAddress + *(int*)(baseFunctionOffset + (ordinal * 4)); + var importname = Marshal.PtrToStringAnsi((IntPtr)functionnameAddress); + //MelonLoader.MelonLogger.Msg($"{imagename}::{importname} @ 0x{((ulong)functionaddress - (ulong)moduleBaseAddress):X} (0x{functionaddress:X} - 0x{moduleBaseAddress:X})"); + if (importname == importName) + return (IntPtr)functionaddress; } } - - return IntPtr.Zero; } - private static unsafe ImageSectionHeader* ImageFirstSection(ImageNtHeaders* ntheader) - { - return (ImageSectionHeader*)((ulong)ntheader + 24 + ntheader->fileHeader.sizeOfOptionalHeader); - } + return IntPtr.Zero; + } + + private static unsafe ImageSectionHeader* ImageFirstSection(ImageNtHeaders* ntheader) + { + return (ImageSectionHeader*)((ulong)ntheader + 24 + ntheader->fileHeader.sizeOfOptionalHeader); } } diff --git a/MelonLoader/Pastel/Pastel.cs b/MelonLoader/Pastel/Pastel.cs index 13b3b39ba..532bc281e 100644 --- a/MelonLoader/Pastel/Pastel.cs +++ b/MelonLoader/Pastel/Pastel.cs @@ -6,207 +6,183 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; -namespace MelonLoader.Pastel -{ - /// - /// Controls colored console output by . - /// - public static class ConsoleExtensions - { - private const int STD_OUTPUT_HANDLE = -11; - private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; - - [DllImport("kernel32.dll")] - private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); - - [DllImport("kernel32.dll")] - private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr GetStdHandle(int nStdHandle); - - - private static bool _enabled; +namespace MelonLoader.Pastel; - private delegate string ColorFormat(string input, Color color); - private delegate string HexColorFormat(string input, string hexColor); - - private enum ColorPlane : byte - { - Foreground, - Background - } +/// +/// Controls colored console output by . +/// +public static class ConsoleExtensions +{ + private const int STD_OUTPUT_HANDLE = -11; + private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; - private const string _formatStringStart = "\u001b[{0};2;"; - private const string _formatStringColor = "{1};{2};{3}m"; - private const string _formatStringContent = "{4}"; - private const string _formatStringEnd = "\u001b[0m"; - private static readonly string _formatStringFull = $"{_formatStringStart}{_formatStringColor}{_formatStringContent}{_formatStringEnd}"; + [DllImport("kernel32.dll")] + private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); + [DllImport("kernel32.dll")] + private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); - private static readonly Dictionary _planeFormatModifiers = new Dictionary - { - [ColorPlane.Foreground] = "38", - [ColorPlane.Background] = "48" - }; + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr GetStdHandle(int nStdHandle); + private static bool _enabled; + private delegate string ColorFormat(string input, Color color); + private delegate string HexColorFormat(string input, string hexColor); - private static readonly Regex _closeNestedPastelStringRegex1 = new Regex($"({_formatStringEnd.Replace("[", @"\[")})+", RegexOptions.Compiled); - private static readonly Regex _closeNestedPastelStringRegex2 = new Regex($"(?().ToArray())})(?:{string.Format(_formatStringStart.Replace("[", @"\["), $"(?:{_planeFormatModifiers[ColorPlane.Foreground]}|{_planeFormatModifiers[ColorPlane.Background]})")})", RegexOptions.Compiled); - - private static readonly Dictionary _closeNestedPastelStringRegex3 = new Dictionary - { - [ColorPlane.Foreground] = new Regex($"(?:{_formatStringEnd.Replace("[", @"\[")})(?!{string.Format(_formatStringStart.Replace("[", @"\["), _planeFormatModifiers[ColorPlane.Foreground])})(?!$)", RegexOptions.Compiled), - [ColorPlane.Background] = new Regex($"(?:{_formatStringEnd.Replace("[", @"\[")})(?!{string.Format(_formatStringStart.Replace("[", @"\["), _planeFormatModifiers[ColorPlane.Background])})(?!$)", RegexOptions.Compiled) - }; + private enum ColorPlane : byte + { + Foreground, + Background + } + private const string _formatStringStart = "\u001b[{0};2;"; + private const string _formatStringColor = "{1};{2};{3}m"; + private const string _formatStringContent = "{4}"; + private const string _formatStringEnd = "\u001b[0m"; + private static readonly string _formatStringFull = $"{_formatStringStart}{_formatStringColor}{_formatStringContent}{_formatStringEnd}"; + private static readonly Dictionary _planeFormatModifiers = new() + { + [ColorPlane.Foreground] = "38", + [ColorPlane.Background] = "48" + }; - private static readonly Func _parseHexColor = hc => int.Parse(hc.Replace("#", ""), NumberStyles.HexNumber); + private static readonly Regex _closeNestedPastelStringRegex1 = new($"({_formatStringEnd.Replace("[", @"\[")})+", RegexOptions.Compiled); + private static readonly Regex _closeNestedPastelStringRegex2 = new($"(?().ToArray())})(?:{string.Format(_formatStringStart.Replace("[", @"\["), $"(?:{_planeFormatModifiers[ColorPlane.Foreground]}|{_planeFormatModifiers[ColorPlane.Background]})")})", RegexOptions.Compiled); - private static readonly Func _colorFormat = (i, c, p) => string.Format(_formatStringFull, _planeFormatModifiers[p], c.R, c.G, c.B, CloseNestedPastelStrings(i, c, p)); - private static readonly Func _colorHexFormat = (i, c, p) => _colorFormat(i, Color.FromArgb(_parseHexColor(c)), p); + private static readonly Dictionary _closeNestedPastelStringRegex3 = new() + { + [ColorPlane.Foreground] = new Regex($"(?:{_formatStringEnd.Replace("[", @"\[")})(?!{string.Format(_formatStringStart.Replace("[", @"\["), _planeFormatModifiers[ColorPlane.Foreground])})(?!$)", RegexOptions.Compiled), + [ColorPlane.Background] = new Regex($"(?:{_formatStringEnd.Replace("[", @"\[")})(?!{string.Format(_formatStringStart.Replace("[", @"\["), _planeFormatModifiers[ColorPlane.Background])})(?!$)", RegexOptions.Compiled) + }; - private static readonly ColorFormat _noColorOutputFormat = (i, _) => i; - private static readonly HexColorFormat _noHexColorOutputFormat = (i, _) => i; + private static readonly Func _parseHexColor = hc => int.Parse(hc.Replace("#", ""), NumberStyles.HexNumber); - private static readonly ColorFormat _foregroundColorFormat = (i, c) => _colorFormat(i, c, ColorPlane.Foreground); - private static readonly HexColorFormat _foregroundHexColorFormat = (i, c) => _colorHexFormat(i, c, ColorPlane.Foreground); + private static readonly Func _colorFormat = (i, c, p) => string.Format(_formatStringFull, _planeFormatModifiers[p], c.R, c.G, c.B, CloseNestedPastelStrings(i, c, p)); + private static readonly Func _colorHexFormat = (i, c, p) => _colorFormat(i, Color.FromArgb(_parseHexColor(c)), p); - private static readonly ColorFormat _backgroundColorFormat = (i, c) => _colorFormat(i, c, ColorPlane.Background); - private static readonly HexColorFormat _backgroundHexColorFormat = (i, c) => _colorHexFormat(i, c, ColorPlane.Background); + private static readonly ColorFormat _noColorOutputFormat = (i, _) => i; + private static readonly HexColorFormat _noHexColorOutputFormat = (i, _) => i; + private static readonly ColorFormat _foregroundColorFormat = (i, c) => _colorFormat(i, c, ColorPlane.Foreground); + private static readonly HexColorFormat _foregroundHexColorFormat = (i, c) => _colorHexFormat(i, c, ColorPlane.Foreground); + private static readonly ColorFormat _backgroundColorFormat = (i, c) => _colorFormat(i, c, ColorPlane.Background); + private static readonly HexColorFormat _backgroundHexColorFormat = (i, c) => _colorHexFormat(i, c, ColorPlane.Background); - private static readonly Dictionary> _colorFormatFuncs = new Dictionary>(new Dictionary> + private static readonly Dictionary> _colorFormatFuncs = new(new Dictionary> + { + [false] = new Dictionary(new Dictionary { - [false] = new Dictionary(new Dictionary - { - [ColorPlane.Foreground] = _noColorOutputFormat, - [ColorPlane.Background] = _noColorOutputFormat - }), - [true] = new Dictionary(new Dictionary - { - [ColorPlane.Foreground] = _foregroundColorFormat, - [ColorPlane.Background] = _backgroundColorFormat - }) - }); - private static readonly Dictionary> _hexColorFormatFuncs = new Dictionary>(new Dictionary> + [ColorPlane.Foreground] = _noColorOutputFormat, + [ColorPlane.Background] = _noColorOutputFormat + }), + [true] = new Dictionary(new Dictionary { - [false] = new Dictionary(new Dictionary - { - [ColorPlane.Foreground] = _noHexColorOutputFormat, - [ColorPlane.Background] = _noHexColorOutputFormat - }), - [true] = new Dictionary(new Dictionary - { - [ColorPlane.Foreground] = _foregroundHexColorFormat, - [ColorPlane.Background] = _backgroundHexColorFormat - }) - }); - - - - - static ConsoleExtensions() + [ColorPlane.Foreground] = _foregroundColorFormat, + [ColorPlane.Background] = _backgroundColorFormat + }) + }); + private static readonly Dictionary> _hexColorFormatFuncs = new(new Dictionary> + { + [false] = new Dictionary(new Dictionary { - if (MelonUtils.IsUnix || MelonUtils.IsMac) - { - Enable(); - return; - } - var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE); - - var enable = GetConsoleMode(iStdOut, out var outConsoleMode) - && SetConsoleMode(iStdOut, outConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); - - - if (Environment.GetEnvironmentVariable("NO_COLOR") == null) - { - Enable(); - } - else - { - Disable(); - } - } - - - - - - - - /// - /// Enables any future console color output produced by Pastel. - /// - public static void Enable() + [ColorPlane.Foreground] = _noHexColorOutputFormat, + [ColorPlane.Background] = _noHexColorOutputFormat + }), + [true] = new Dictionary(new Dictionary { - _enabled = true; - } + [ColorPlane.Foreground] = _foregroundHexColorFormat, + [ColorPlane.Background] = _backgroundHexColorFormat + }) + }); - /// - /// Disables any future console color output produced by Pastel. - /// - public static void Disable() + static ConsoleExtensions() + { + if (MelonUtils.IsUnix || MelonUtils.IsMac) { - _enabled = false; + Enable(); + return; } + var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + _ = GetConsoleMode(iStdOut, out var outConsoleMode) + && SetConsoleMode(iStdOut, outConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); - /// - /// Returns a string wrapped in an ANSI foreground color code using the specified color. - /// - /// The string to color. - /// The color to use on the specified string. - public static string Pastel(this string input, Color color) + if (Environment.GetEnvironmentVariable("NO_COLOR") == null) { - return _colorFormatFuncs[_enabled][ColorPlane.Foreground](input, color); + Enable(); } - - /// - /// Returns a string wrapped in an ANSI foreground color code using the specified color. - /// - /// The string to color. - /// The color to use on the specified string.Supported format: [#]RRGGBB. - public static string Pastel(this string input, string hexColor) + else { - return _hexColorFormatFuncs[_enabled][ColorPlane.Foreground](input, hexColor); + Disable(); } + } + /// + /// Enables any future console color output produced by Pastel. + /// + public static void Enable() + { + _enabled = true; + } + /// + /// Disables any future console color output produced by Pastel. + /// + public static void Disable() + { + _enabled = false; + } - /// - /// Returns a string wrapped in an ANSI background color code using the specified color. - /// - /// The string to color. - /// The color to use on the specified string. - public static string PastelBg(this string input, Color color) - { - return _colorFormatFuncs[_enabled][ColorPlane.Background](input, color); - } + /// + /// Returns a string wrapped in an ANSI foreground color code using the specified color. + /// + /// The string to color. + /// The color to use on the specified string. + public static string Pastel(this string input, Color color) + { + return _colorFormatFuncs[_enabled][ColorPlane.Foreground](input, color); + } - /// - /// Returns a string wrapped in an ANSI background color code using the specified color. - /// - /// The string to color. - /// The color to use on the specified string.Supported format: [#]RRGGBB. - public static string PastelBg(this string input, string hexColor) - { - return _hexColorFormatFuncs[_enabled][ColorPlane.Background](input, hexColor); - } + /// + /// Returns a string wrapped in an ANSI foreground color code using the specified color. + /// + /// The string to color. + /// The color to use on the specified string.Supported format: [#]RRGGBB. + public static string Pastel(this string input, string hexColor) + { + return _hexColorFormatFuncs[_enabled][ColorPlane.Foreground](input, hexColor); + } + /// + /// Returns a string wrapped in an ANSI background color code using the specified color. + /// + /// The string to color. + /// The color to use on the specified string. + public static string PastelBg(this string input, Color color) + { + return _colorFormatFuncs[_enabled][ColorPlane.Background](input, color); + } + /// + /// Returns a string wrapped in an ANSI background color code using the specified color. + /// + /// The string to color. + /// The color to use on the specified string.Supported format: [#]RRGGBB. + public static string PastelBg(this string input, string hexColor) + { + return _hexColorFormatFuncs[_enabled][ColorPlane.Background](input, hexColor); + } - private static string CloseNestedPastelStrings(string input, Color color, ColorPlane colorPlane) - { - var closedString = _closeNestedPastelStringRegex1.Replace(input, _formatStringEnd); + private static string CloseNestedPastelStrings(string input, Color color, ColorPlane colorPlane) + { + var closedString = _closeNestedPastelStringRegex1.Replace(input, _formatStringEnd); - closedString = _closeNestedPastelStringRegex2.Replace(closedString, $"{_formatStringEnd}$0"); - closedString = _closeNestedPastelStringRegex3[colorPlane].Replace(closedString, $"$0{string.Format($"{_formatStringStart}{_formatStringColor}", _planeFormatModifiers[colorPlane], color.R, color.G, color.B)}"); + closedString = _closeNestedPastelStringRegex2.Replace(closedString, $"{_formatStringEnd}$0"); + closedString = _closeNestedPastelStringRegex3[colorPlane].Replace(closedString, $"$0{string.Format($"{_formatStringStart}{_formatStringColor}", _planeFormatModifiers[colorPlane], color.R, color.G, color.B)}"); - return closedString; - } + return closedString; } } \ No newline at end of file diff --git a/MelonLoader/PatchShield.cs b/MelonLoader/PatchShield.cs index 3a377f9ff..fa970b63a 100644 --- a/MelonLoader/PatchShield.cs +++ b/MelonLoader/PatchShield.cs @@ -1,62 +1,70 @@ -using System; +using HarmonyLib; +using MonoMod.RuntimeDetour; +using System; using System.Linq; using System.Reflection; -using HarmonyLib; -using MonoMod.RuntimeDetour; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)] +public class PatchShield : Attribute //Naming violation? { - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)] - public class PatchShield : Attribute //Naming violation? - { - private static AccessTools.FieldRef PatchProcessor_OriginalRef; + private static AccessTools.FieldRef PatchProcessor_OriginalRef; + + private static void LogException(Exception ex) => MelonLogger.Warning($"Patch Shield Exception: {ex}"); - private static void LogException(Exception ex) => MelonLogger.Warning($"Patch Shield Exception: {ex}"); + private static bool MethodCheck(MethodBase method) => + (method != null) + && (method.DeclaringType.Assembly.GetCustomAttributes(typeof(PatchShield), false).Length <= 0) + && (method.DeclaringType.GetCustomAttributes(typeof(PatchShield), false).Length <= 0) + && (method.GetCustomAttributes(typeof(PatchShield), false).Length <= 0); - private static bool MethodCheck(MethodBase method) => - (method != null) - && (method.DeclaringType.Assembly.GetCustomAttributes(typeof(PatchShield), false).Length <= 0) - && (method.DeclaringType.GetCustomAttributes(typeof(PatchShield), false).Length <= 0) - && (method.GetCustomAttributes(typeof(PatchShield), false).Length <= 0); + internal static void Install() + { + var patchProcessorType = typeof(PatchProcessor); + var patchShieldType = typeof(PatchShield); + PatchProcessor_OriginalRef = AccessTools.FieldRefAccess(patchProcessorType, "original"); + + try + { + Core.HarmonyInstance.Patch( + AccessTools.Method("HarmonyLib.PatchFunctions:ReversePatch"), + AccessTools.Method(patchShieldType, "PatchMethod_PatchFunctions_ReversePatch").ToNewHarmonyMethod() + ); + } + catch (Exception ex) + { + LogException(ex); + } - internal static void Install() + try { - Type patchProcessorType = typeof(PatchProcessor); - Type patchShieldType = typeof(PatchShield); - PatchProcessor_OriginalRef = AccessTools.FieldRefAccess(patchProcessorType, "original"); - - try - { - Core.HarmonyInstance.Patch( - AccessTools.Method("HarmonyLib.PatchFunctions:ReversePatch"), - AccessTools.Method(patchShieldType, "PatchMethod_PatchFunctions_ReversePatch").ToNewHarmonyMethod() - ); - } - catch (Exception ex) { LogException(ex); } - - try - { - HarmonyMethod unpatchMethod = AccessTools.Method(patchShieldType, "PatchMethod_PatchProcessor_Unpatch").ToNewHarmonyMethod(); - foreach (MethodInfo method in patchProcessorType.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name.Equals("Unpatch"))) - Core.HarmonyInstance.Patch(method, unpatchMethod); - } - catch (Exception ex) { LogException(ex); } - - try - { - Core.HarmonyInstance.Patch(AccessTools.Method(patchProcessorType, "Patch"), - AccessTools.Method(patchShieldType, "PatchMethod_PatchProcessor_Patch").ToNewHarmonyMethod() - ); - } - catch (Exception ex) { LogException(ex); } - - Hook.OnDetour += (detour, originalMethod, patchMethod, delegateTarget) => MethodCheck(originalMethod); - ILHook.OnDetour += (detour, originalMethod, ilmanipulator) => MethodCheck(originalMethod); - Detour.OnDetour += (detour, originalMethod, patchMethod) => MethodCheck(originalMethod); - } - - private static bool PatchMethod_PatchFunctions_ReversePatch(MethodBase __1) => MethodCheck(__1); - private static bool PatchMethod_PatchProcessor_Patch(PatchProcessor __instance) => MethodCheck(PatchProcessor_OriginalRef(__instance)); - private static bool PatchMethod_PatchProcessor_Unpatch(PatchProcessor __instance) => MethodCheck(PatchProcessor_OriginalRef(__instance)); - } + var unpatchMethod = AccessTools.Method(patchShieldType, "PatchMethod_PatchProcessor_Unpatch").ToNewHarmonyMethod(); + foreach (var method in patchProcessorType.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name.Equals("Unpatch"))) + Core.HarmonyInstance.Patch(method, unpatchMethod); + } + catch (Exception ex) + { + LogException(ex); + } + + try + { + Core.HarmonyInstance.Patch(AccessTools.Method(patchProcessorType, "Patch"), + AccessTools.Method(patchShieldType, "PatchMethod_PatchProcessor_Patch").ToNewHarmonyMethod() + ); + } + catch (Exception ex) + { + LogException(ex); + } + + Hook.OnDetour += (detour, originalMethod, patchMethod, delegateTarget) => MethodCheck(originalMethod); + ILHook.OnDetour += (detour, originalMethod, ilmanipulator) => MethodCheck(originalMethod); + Detour.OnDetour += (detour, originalMethod, patchMethod) => MethodCheck(originalMethod); + } + + private static bool PatchMethod_PatchFunctions_ReversePatch(MethodBase __1) => MethodCheck(__1); + private static bool PatchMethod_PatchProcessor_Patch(PatchProcessor __instance) => MethodCheck(PatchProcessor_OriginalRef(__instance)); + private static bool PatchMethod_PatchProcessor_Unpatch(PatchProcessor __instance) => MethodCheck(PatchProcessor_OriginalRef(__instance)); } \ No newline at end of file diff --git a/MelonLoader/Preferences/IO/File.cs b/MelonLoader/Preferences/IO/File.cs index 698cdab27..f171fc5a1 100644 --- a/MelonLoader/Preferences/IO/File.cs +++ b/MelonLoader/Preferences/IO/File.cs @@ -3,226 +3,225 @@ using Tomlet.Exceptions; using Tomlet.Models; -namespace MelonLoader.Preferences.IO +namespace MelonLoader.Preferences.IO; + +internal class File { - internal class File - { - private bool _waserror = false; + private bool _waserror = false; - internal bool WasError + internal bool WasError + { + get => _waserror; + set { - get => _waserror; - set + if (value == true) { - if (value == true) - { - MelonLogger.Warning($"Defaulting {FilePath} to Fallback Functionality to further avoid File Corruption..."); - IsSaving = false; - FileWatcher.Destroy(); - } - - _waserror = value; + MelonLogger.Warning($"Defaulting {FilePath} to Fallback Functionality to further avoid File Corruption..."); + IsSaving = false; + FileWatcher.Destroy(); } + + _waserror = value; } + } + + internal string FilePath = null; + internal string LegacyFilePath = null; + internal bool IsSaving = false; + internal bool ShouldSave = true; + internal TomlDocument document = TomlDocument.CreateEmpty(); + internal Watcher FileWatcher = null; - internal string FilePath = null; - internal string LegacyFilePath = null; - internal bool IsSaving = false; - internal bool ShouldSave = true; - internal TomlDocument document = TomlDocument.CreateEmpty(); - internal Watcher FileWatcher = null; + internal File(string filepath, string legacyfilepath = null, bool shouldsave = true) + { + FilePath = filepath; + LegacyFilePath = legacyfilepath; + ShouldSave = shouldsave; + FileWatcher = new Watcher(this); + } + + internal void LegacyLoad() + { + if (string.IsNullOrEmpty(LegacyFilePath) || !System.IO.File.Exists(LegacyFilePath)) + return; + var filestr = System.IO.File.ReadAllText(LegacyFilePath); + var lines = filestr.Split('\n'); + string category = null; - internal File(string filepath, string legacyfilepath = null, bool shouldsave = true) + foreach (var line in lines) { - FilePath = filepath; - LegacyFilePath = legacyfilepath; - ShouldSave = shouldsave; - FileWatcher = new Watcher(this); + if (string.IsNullOrEmpty(line)) + continue; + var newline = line.Replace("\n", "").Replace("\r", "").Replace(" ", ""); + if (newline.Contains("[") && newline.Contains("]")) + { + category = newline.Replace("[", "").Replace("]", ""); + continue; + } + + if (!newline.Contains("=")) + continue; + var parts = line.Split('='); + if (string.IsNullOrEmpty(parts[0]) || string.IsNullOrEmpty(parts[1])) + continue; + if (parts[1].ToLower().StartsWith("true") || parts[1].ToLower().StartsWith("false")) + InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(parts[1].ToLower().StartsWith("true"))); + else if (int.TryParse(parts[1], out var val_int)) + InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(val_int)); + else if (float.TryParse(parts[1], out var val_float)) + InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(val_float)); + else + InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(parts[1].Replace("\r", ""))); } + } - internal void LegacyLoad() - { - if (string.IsNullOrEmpty(LegacyFilePath) || !System.IO.File.Exists(LegacyFilePath)) - return; - string filestr = System.IO.File.ReadAllText(LegacyFilePath); - string[] lines = filestr.Split('\n'); - string category = null; + internal void Load() + { + if (_waserror) + return; + if (!System.IO.File.Exists(FilePath)) + return; + document = TomlParser.ParseFile(FilePath); + } - foreach (string line in lines) - { - if (string.IsNullOrEmpty(line)) - continue; - string newline = line.Replace("\n", "").Replace("\r", "").Replace(" ", ""); - if (newline.Contains("[") && newline.Contains("]")) - { - category = newline.Replace("[", "").Replace("]", ""); - continue; - } - - if (!newline.Contains("=")) - continue; - string[] parts = line.Split('='); - if (string.IsNullOrEmpty(parts[0]) || string.IsNullOrEmpty(parts[1])) - continue; - if (parts[1].ToLower().StartsWith("true") || parts[1].ToLower().StartsWith("false")) - InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(parts[1].ToLower().StartsWith("true"))); - else if (int.TryParse(parts[1], out int val_int)) - InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(val_int)); - else if (float.TryParse(parts[1], out float val_float)) - InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(val_float)); - else - InsertIntoDocument(category, parts[0], TomletMain.ValueFrom(parts[1].Replace("\r", ""))); - } - } + internal void Save() + { + if (_waserror || !ShouldSave) + return; + IsSaving = true; + System.IO.File.WriteAllText(FilePath, document.SerializedValue); + if ((LegacyFilePath != null) && System.IO.File.Exists(LegacyFilePath)) + System.IO.File.Delete(LegacyFilePath); + } + + private static string QuoteKey(string key) => + key.Contains('"') + ? $"'{key}'" + : $"\"{key}\""; + + internal void InsertIntoDocument(string category, string key, TomlValue value, bool should_inline = false) + { + if (!document.ContainsKey(category)) + document.PutValue(category, new TomlTable()); - internal void Load() + try + { + var categoryTable = document.GetSubTable(category); + categoryTable.ForceNoInline = !should_inline; + categoryTable.PutValue(QuoteKey(key), value); + } + catch (TomlTypeMismatchException) { - if (_waserror) - return; - if (!System.IO.File.Exists(FilePath)) - return; - document = TomlParser.ParseFile(FilePath); + //Ignore } + catch (TomlNoSuchValueException) + { + //Ignore + } + } - internal void Save() + internal bool RemoveEntryFromDocument(string category, string key) + { + if (!document.ContainsKey(category)) + return false; + + try + { + var categoryTable = document.GetSubTable(category); + return categoryTable.Entries.Remove(key); + } + catch (TomlTypeMismatchException) + { + return false; + } + catch (TomlNoSuchValueException) { - if (_waserror || !ShouldSave) - return; - IsSaving = true; - System.IO.File.WriteAllText(FilePath, document.SerializedValue); - if ((LegacyFilePath != null) && System.IO.File.Exists(LegacyFilePath)) - System.IO.File.Delete(LegacyFilePath); + return false; } - - private static string QuoteKey(string key) => - key.Contains('"') - ? $"'{key}'" - : $"\"{key}\""; + } - internal void InsertIntoDocument(string category, string key, TomlValue value, bool should_inline = false) + internal bool RemoveCategoryFromDocument(string category) + { + if (!document.ContainsKey(category)) + return false; + try { - if (!document.ContainsKey(category)) - document.PutValue(category, new TomlTable()); - - try - { - var categoryTable = document.GetSubTable(category); - categoryTable.ForceNoInline = !should_inline; - categoryTable.PutValue(QuoteKey(key), value); - } - catch (TomlTypeMismatchException) - { - //Ignore - } - catch (TomlNoSuchValueException) - { - //Ignore - } + return document.Entries.Remove(category); + } + catch (TomlTypeMismatchException) + { + return false; + } + catch (TomlNoSuchValueException) + { + return false; } + } + + internal bool RenameEntryInDocument(string category, string key, string newKey) + { + if (!document.ContainsKey(category)) + return false; - internal bool RemoveEntryFromDocument(string category, string key) + try { - if (!document.ContainsKey(category)) + var categoryTable = document.GetSubTable(category); + if (!categoryTable.Entries.ContainsKey(key) || categoryTable.Entries.ContainsKey(newKey)) return false; - try - { - var categoryTable = document.GetSubTable(category); - return categoryTable.Entries.Remove(key); - } - catch (TomlTypeMismatchException) - { - return false; - } - catch (TomlNoSuchValueException) - { - return false; - } + var value = categoryTable.Entries[key]; + categoryTable.Entries.Remove(key); + categoryTable.Entries.Add(newKey, value); + return true; + } + catch (TomlTypeMismatchException) + { + return false; + } + catch (TomlNoSuchValueException) + { + return false; } + } - internal bool RemoveCategoryFromDocument(string category) + internal TomlTable TryGetCategoryTable(string category) + { + lock (document) { - if (!document.ContainsKey(category)) - return false; try { - return document.Entries.Remove(category); + return document.GetSubTable(category); } catch (TomlTypeMismatchException) { - return false; + //Ignore } catch (TomlNoSuchValueException) { - return false; + //Ignore } + + return null; } + } - internal bool RenameEntryInDocument(string category, string key, string newKey) + internal void SetupEntryFromRawValue(MelonPreferences_Entry entry) + { + lock (document) { - if (!document.ContainsKey(category)) - return false; - try { - var categoryTable = document.GetSubTable(category); - if (!categoryTable.Entries.ContainsKey(key) || categoryTable.Entries.ContainsKey(newKey)) - return false; - - TomlValue value = categoryTable.Entries[key]; - categoryTable.Entries.Remove(key); - categoryTable.Entries.Add(newKey, value); - return true; + var categoryTable = document.GetSubTable(entry.Category.Identifier); + var value = categoryTable.GetValue(QuoteKey(entry.Identifier)); + entry.Load(value); } catch (TomlTypeMismatchException) { - return false; + //Ignore } catch (TomlNoSuchValueException) { - return false; - } - } - - internal TomlTable TryGetCategoryTable(string category) - { - lock (document) - { - try - { - return document.GetSubTable(category); - } - catch (TomlTypeMismatchException) - { - //Ignore - } - catch (TomlNoSuchValueException) - { - //Ignore - } - - return null; - } - } - - internal void SetupEntryFromRawValue(MelonPreferences_Entry entry) - { - lock (document) - { - try - { - var categoryTable = document.GetSubTable(entry.Category.Identifier); - var value = categoryTable.GetValue(QuoteKey(entry.Identifier)); - entry.Load(value); - } - catch (TomlTypeMismatchException) - { - //Ignore - } - catch (TomlNoSuchValueException) - { - //Ignore - } + //Ignore } } } diff --git a/MelonLoader/Preferences/IO/Watcher.cs b/MelonLoader/Preferences/IO/Watcher.cs index 7cb308e65..46aa6535f 100644 --- a/MelonLoader/Preferences/IO/Watcher.cs +++ b/MelonLoader/Preferences/IO/Watcher.cs @@ -1,75 +1,74 @@ -using System; +using HarmonyLib; +using System; using System.IO; using System.Reflection; -using HarmonyLib; -namespace MelonLoader.Preferences.IO +namespace MelonLoader.Preferences.IO; + +internal class Watcher { - internal class Watcher - { - private static bool ShouldDisableFileWatcherFunctionality = false; - private FileSystemWatcher FileWatcher = null; - private readonly File PrefFile = null; + private static bool ShouldDisableFileWatcherFunctionality = false; + private FileSystemWatcher FileWatcher = null; + private readonly File PrefFile = null; - internal Watcher(File preffile) + internal Watcher(File preffile) + { + PrefFile = preffile; + if (ShouldDisableFileWatcherFunctionality) + return; + try { - PrefFile = preffile; - if (ShouldDisableFileWatcherFunctionality) - return; - try + var method = AccessTools.PropertyGetter(typeof(FileSystemWatcher), "Path"); + if (method == null) + throw new NullReferenceException("No Path Property Get Method Found!"); + if (method.IsNotImplemented()) { - MethodInfo method = AccessTools.PropertyGetter(typeof(FileSystemWatcher), "Path"); - if (method == null) - throw new NullReferenceException("No Path Property Get Method Found!"); - if (method.IsNotImplemented()) - { - MelonLogger.Warning("FileSystemWatcher NotImplementedException Detected! Disabling MelonPreferences FileWatcher Functionality..."); - ShouldDisableFileWatcherFunctionality = true; - return; - } - - FileWatcher = new FileSystemWatcher(Path.GetDirectoryName(preffile.FilePath), Path.GetFileName(preffile.FilePath)) - { - NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite, - EnableRaisingEvents = true - }; - FileWatcher.Created += new FileSystemEventHandler(OnFileWatcherTriggered); - FileWatcher.Changed += new FileSystemEventHandler(OnFileWatcherTriggered); - FileWatcher.BeginInit(); - } - catch (Exception ex) - { - MelonLogger.Warning("FileSystemWatcher Exception: " + ex.ToString()); + MelonLogger.Warning("FileSystemWatcher NotImplementedException Detected! Disabling MelonPreferences FileWatcher Functionality..."); ShouldDisableFileWatcherFunctionality = true; - FileWatcher = null; - } - } - - internal void Destroy() - { - if (ShouldDisableFileWatcherFunctionality || (FileWatcher == null)) return; - try - { - FileWatcher.EndInit(); - FileWatcher.Dispose(); } - catch (Exception ex) + + FileWatcher = new FileSystemWatcher(Path.GetDirectoryName(preffile.FilePath), Path.GetFileName(preffile.FilePath)) { - MelonLogger.Warning("FileSystemWatcher Exception: " + ex.ToString()); - ShouldDisableFileWatcherFunctionality = true; - } + NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite, + EnableRaisingEvents = true + }; + FileWatcher.Created += new FileSystemEventHandler(OnFileWatcherTriggered); + FileWatcher.Changed += new FileSystemEventHandler(OnFileWatcherTriggered); + FileWatcher.BeginInit(); + } + catch (Exception ex) + { + MelonLogger.Warning("FileSystemWatcher Exception: " + ex.ToString()); + ShouldDisableFileWatcherFunctionality = true; FileWatcher = null; } + } - private void OnFileWatcherTriggered(object source, FileSystemEventArgs e) + internal void Destroy() + { + if (ShouldDisableFileWatcherFunctionality || (FileWatcher == null)) + return; + try { - if (PrefFile.IsSaving) - { - PrefFile.IsSaving = false; - return; - } - MelonPreferences.LoadFileAndRefreshCategories(PrefFile); + FileWatcher.EndInit(); + FileWatcher.Dispose(); + } + catch (Exception ex) + { + MelonLogger.Warning("FileSystemWatcher Exception: " + ex.ToString()); + ShouldDisableFileWatcherFunctionality = true; + } + FileWatcher = null; + } + + private void OnFileWatcherTriggered(object source, FileSystemEventArgs e) + { + if (PrefFile.IsSaving) + { + PrefFile.IsSaving = false; + return; } + MelonPreferences.LoadFileAndRefreshCategories(PrefFile); } } diff --git a/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs b/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs index ad0ba09ac..2dc3e148c 100644 --- a/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs +++ b/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs @@ -3,141 +3,141 @@ using Tomlet.Exceptions; using Tomlet.Models; -namespace MelonLoader.Preferences +namespace MelonLoader.Preferences; + +public class MelonPreferences_ReflectiveCategory { - public class MelonPreferences_ReflectiveCategory + private readonly Type SystemType; + private object value; + internal IO.File File = null; + + public string Identifier { get; internal set; } + public string DisplayName { get; internal set; } + + internal static MelonPreferences_ReflectiveCategory Create(string categoryName, string displayName) => new(typeof(T), categoryName, displayName); + + private MelonPreferences_ReflectiveCategory(Type type, string categoryName, string displayName) { - private Type SystemType; - private object value; - internal IO.File File = null; - - public string Identifier { get; internal set; } - public string DisplayName { get; internal set; } - - internal static MelonPreferences_ReflectiveCategory Create(string categoryName, string displayName) => new MelonPreferences_ReflectiveCategory(typeof(T), categoryName, displayName); - - private MelonPreferences_ReflectiveCategory(Type type, string categoryName, string displayName) - { - SystemType = type; - Identifier = categoryName; - DisplayName = displayName; - - IO.File currentFile = File; - if (currentFile == null) - currentFile = MelonPreferences.DefaultFile; - if (!(currentFile.TryGetCategoryTable(Identifier) is { } table)) - LoadDefaults(); - else - Load(table); - - MelonPreferences.ReflectiveCategories.Add(this); - } + SystemType = type; + Identifier = categoryName; + DisplayName = displayName; - internal void LoadDefaults() => value = Activator.CreateInstance(SystemType); + var currentFile = File; + currentFile ??= MelonPreferences.DefaultFile; + if (currentFile.TryGetCategoryTable(Identifier) is not + { } table) + LoadDefaults(); + else + Load(table); - internal void Load(TomlValue tomlValue) + MelonPreferences.ReflectiveCategories.Add(this); + } + + internal void LoadDefaults() => value = Activator.CreateInstance(SystemType); + + internal void Load(TomlValue tomlValue) + { + try { - try { value = TomletMain.To(SystemType, tomlValue); } - catch (TomlTypeMismatchException) - { - return; - } - catch (TomlNoSuchValueException) - { - return; - } - catch (TomlEnumParseException) - { - return; - } + value = TomletMain.To(SystemType, tomlValue); } - - internal TomlValue Save() + catch (TomlTypeMismatchException) { - if(value == null) - LoadDefaults(); - - return TomletMain.ValueFrom(SystemType, value); + return; } - - public T GetValue() where T : new() + catch (TomlNoSuchValueException) { - if (typeof(T) != SystemType) - return default; - if (value == null) - LoadDefaults(); - return (T) value; + return; } - - public void SetFilePath(string filepath, bool autoload = true, bool printmsg = true) + catch (TomlEnumParseException) { - if (File != null) - { - IO.File oldfile = File; - File = null; - if (!MelonPreferences.IsFileInUse(oldfile)) - { - oldfile.FileWatcher.Destroy(); - MelonPreferences.PrefFiles.Remove(oldfile); - } - } - if (!string.IsNullOrEmpty(filepath) && !MelonPreferences.IsFilePathDefault(filepath)) - { - File = MelonPreferences.GetPrefFileFromFilePath(filepath); - if (File == null) - { - File = new IO.File(filepath); - MelonPreferences.PrefFiles.Add(File); - } - } - if (autoload) - MelonPreferences.LoadFileAndRefreshCategories(File, printmsg); + return; } + } + + internal TomlValue Save() + { + if (value == null) + LoadDefaults(); - public void ResetFilePath() + return TomletMain.ValueFrom(SystemType, value); + } + + public T GetValue() where T : new() + { + if (typeof(T) != SystemType) + return default; + if (value == null) + LoadDefaults(); + return (T)value; + } + + public void SetFilePath(string filepath, bool autoload = true, bool printmsg = true) + { + if (File != null) { - if (File == null) - return; - IO.File oldfile = File; + var oldfile = File; File = null; if (!MelonPreferences.IsFileInUse(oldfile)) { oldfile.FileWatcher.Destroy(); MelonPreferences.PrefFiles.Remove(oldfile); } - MelonPreferences.LoadFileAndRefreshCategories(MelonPreferences.DefaultFile); } - - public void SaveToFile(bool printmsg = true) + if (!string.IsNullOrEmpty(filepath) && !MelonPreferences.IsFilePathDefault(filepath)) { - IO.File currentfile = File; - if (currentfile == null) - currentfile = MelonPreferences.DefaultFile; - - currentfile.document.PutValue(Identifier, Save()); - try - { - currentfile.Save(); - } - catch (Exception ex) + File = MelonPreferences.GetPrefFileFromFilePath(filepath); + if (File == null) { - MelonLogger.Error($"Error while Saving Preferences to {currentfile.FilePath}: {ex}"); - currentfile.WasError = true; + File = new IO.File(filepath); + MelonPreferences.PrefFiles.Add(File); } - if (printmsg) - MelonLogger.Msg($"MelonPreferences Saved to {currentfile.FilePath}"); + } + if (autoload) + MelonPreferences.LoadFileAndRefreshCategories(File, printmsg); + } - MelonPreferences.OnPreferencesSaved.Invoke(currentfile.FilePath); + public void ResetFilePath() + { + if (File == null) + return; + var oldfile = File; + File = null; + if (!MelonPreferences.IsFileInUse(oldfile)) + { + oldfile.FileWatcher.Destroy(); + MelonPreferences.PrefFiles.Remove(oldfile); } + MelonPreferences.LoadFileAndRefreshCategories(MelonPreferences.DefaultFile); + } + + public void SaveToFile(bool printmsg = true) + { + var currentfile = File; + currentfile ??= MelonPreferences.DefaultFile; - public void LoadFromFile(bool printmsg = true) + currentfile.document.PutValue(Identifier, Save()); + try { - IO.File currentfile = File; - if (currentfile == null) - currentfile = MelonPreferences.DefaultFile; - MelonPreferences.LoadFileAndRefreshCategories(currentfile, printmsg); + currentfile.Save(); } + catch (Exception ex) + { + MelonLogger.Error($"Error while Saving Preferences to {currentfile.FilePath}: {ex}"); + currentfile.WasError = true; + } + if (printmsg) + MelonLogger.Msg($"MelonPreferences Saved to {currentfile.FilePath}"); + + MelonPreferences.OnPreferencesSaved.Invoke(currentfile.FilePath); + } - public void DestroyFileWatcher() => File?.FileWatcher.Destroy(); + public void LoadFromFile(bool printmsg = true) + { + var currentfile = File; + currentfile ??= MelonPreferences.DefaultFile; + MelonPreferences.LoadFileAndRefreshCategories(currentfile, printmsg); } + + public void DestroyFileWatcher() => File?.FileWatcher.Destroy(); } \ No newline at end of file diff --git a/MelonLoader/Preferences/ValueValidator.cs b/MelonLoader/Preferences/ValueValidator.cs index b9194b652..ec591c482 100644 --- a/MelonLoader/Preferences/ValueValidator.cs +++ b/MelonLoader/Preferences/ValueValidator.cs @@ -1,46 +1,43 @@ using System; -namespace MelonLoader.Preferences +namespace MelonLoader.Preferences; + +public abstract class ValueValidator { - public abstract class ValueValidator - { - public abstract bool IsValid(object value); - public abstract object EnsureValid(object value); - } + public abstract bool IsValid(object value); + public abstract object EnsureValid(object value); +} + +public interface IValueRange +{ + object MinValue { get; } + object MaxValue { get; } +} + +public class ValueRange : ValueValidator, IValueRange where T : IComparable +{ + public T MinValue { get; } + public T MaxValue { get; } - public interface IValueRange + public ValueRange(T minValue, T maxValue) { - object MinValue { get; } - object MaxValue { get; } + if (minValue.CompareTo(maxValue) >= 0) + throw new ArgumentException($"Min value ({minValue}) must be less than max value ({maxValue})!"); + + MinValue = minValue; + MaxValue = maxValue; } - public class ValueRange : ValueValidator, IValueRange where T : IComparable + public override bool IsValid(object value) + => MaxValue.CompareTo(value) >= 0 && MinValue.CompareTo(value) <= 0; + + public override object EnsureValid(object value) { - public T MinValue { get; } - public T MaxValue { get; } - - public ValueRange(T minValue, T maxValue) - { - if (minValue.CompareTo(maxValue) >= 0) - throw new ArgumentException($"Min value ({minValue}) must be less than max value ({maxValue})!"); - - MinValue = minValue; - MaxValue = maxValue; - } - - public override bool IsValid(object value) - => MaxValue.CompareTo(value) >= 0 && MinValue.CompareTo(value) <= 0; - - public override object EnsureValid(object value) - { - if (MaxValue.CompareTo(value) < 0) - return MaxValue; - if (MinValue.CompareTo(value) > 0) - return MinValue; - return value; - } - - object IValueRange.MinValue => MinValue; - object IValueRange.MaxValue => MaxValue; + if (MaxValue.CompareTo(value) < 0) + return MaxValue; + return MinValue.CompareTo(value) > 0 ? MinValue : value; } + + object IValueRange.MinValue => MinValue; + object IValueRange.MaxValue => MaxValue; } diff --git a/MelonLoader/Properties/BuildInfo.cs b/MelonLoader/Properties/BuildInfo.cs index 3224da9b9..12243dd22 100644 --- a/MelonLoader/Properties/BuildInfo.cs +++ b/MelonLoader/Properties/BuildInfo.cs @@ -17,9 +17,9 @@ public static class BuildInfo static BuildInfo() { var version = typeof(BuildInfo).Assembly.GetName().Version!; - VersionNumber = new(version.Major, version.Minor, version.Build, ((version.Revision == 0) + VersionNumber = new(version.Major, version.Minor, version.Build, (version.Revision == 0) ? "" - : ("ci." + version.Revision.ToString()))); + : ("ci." + version.Revision.ToString())); Version = VersionNumber.ToString(); } } \ No newline at end of file diff --git a/MelonLoader/RegisterTypeInIl2Cpp.cs b/MelonLoader/RegisterTypeInIl2Cpp.cs index 16ecb6cae..eca7ffc4e 100644 --- a/MelonLoader/RegisterTypeInIl2Cpp.cs +++ b/MelonLoader/RegisterTypeInIl2Cpp.cs @@ -3,55 +3,54 @@ using System.Linq; using System.Reflection; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Class)] +public class RegisterTypeInIl2Cpp : Attribute //Naming violation? { - [AttributeUsage(AttributeTargets.Class)] - public class RegisterTypeInIl2Cpp : Attribute //Naming violation? - { - internal static List registrationQueue = new List(); - internal static bool ready; - internal bool LogSuccess = true; + internal static List registrationQueue = []; + internal static bool ready; + internal bool LogSuccess = true; - public RegisterTypeInIl2Cpp() { } - public RegisterTypeInIl2Cpp(bool logSuccess) { LogSuccess = logSuccess; } + public RegisterTypeInIl2Cpp() { } + public RegisterTypeInIl2Cpp(bool logSuccess) { LogSuccess = logSuccess; } - public static void RegisterAssembly(Assembly asm) + public static void RegisterAssembly(Assembly asm) + { + if (!MelonUtils.IsGameIl2Cpp()) + return; + + if (!ready) { - if (!MelonUtils.IsGameIl2Cpp()) - return; - - if (!ready) - { - registrationQueue.Add(asm); - return; - } - - IEnumerable typeTbl = asm.GetValidTypes(); - if ((typeTbl == null) || (typeTbl.Count() <= 0)) - return; - foreach (Type type in typeTbl) - { - object[] attTbl = type.GetCustomAttributes(typeof(RegisterTypeInIl2Cpp), false); - if ((attTbl == null) || (attTbl.Length <= 0)) - continue; - RegisterTypeInIl2Cpp att = (RegisterTypeInIl2Cpp)attTbl[0]; - if (att == null) - continue; - InteropSupport.RegisterTypeInIl2CppDomain(type, att.LogSuccess); - } + registrationQueue.Add(asm); + return; } - internal static void SetReady() + var typeTbl = asm.GetValidTypes(); + if ((typeTbl == null) || (typeTbl.Count() <= 0)) + return; + foreach (var type in typeTbl) { - ready = true; + var attTbl = type.GetCustomAttributes(typeof(RegisterTypeInIl2Cpp), false); + if ((attTbl == null) || (attTbl.Length <= 0)) + continue; + var att = (RegisterTypeInIl2Cpp)attTbl[0]; + if (att == null) + continue; + InteropSupport.RegisterTypeInIl2CppDomain(type, att.LogSuccess); + } + } + + internal static void SetReady() + { + ready = true; - if (registrationQueue == null) - return; + if (registrationQueue == null) + return; - foreach (var asm in registrationQueue) - RegisterAssembly(asm); + foreach (var asm in registrationQueue) + RegisterAssembly(asm); - registrationQueue = null; - } + registrationQueue = null; } } \ No newline at end of file diff --git a/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs b/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs index 5a1c679d9..bb1f121d5 100644 --- a/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs +++ b/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs @@ -3,86 +3,85 @@ using System.Linq; using System.Reflection; -namespace MelonLoader +namespace MelonLoader; + +[AttributeUsage(AttributeTargets.Class)] +public class RegisterTypeInIl2CppWithInterfaces : Attribute //Naming violation? { - [AttributeUsage(AttributeTargets.Class)] - public class RegisterTypeInIl2CppWithInterfaces : Attribute //Naming violation? + internal static List registrationQueue = []; + internal static bool ready; + internal bool LogSuccess = true; + + internal Type[] Interfaces; + internal bool GetInterfacesFromType; + + public RegisterTypeInIl2CppWithInterfaces() { - internal static List registrationQueue = new List(); - internal static bool ready; - internal bool LogSuccess = true; + GetInterfacesFromType = true; + } - internal Type[] Interfaces; - internal bool GetInterfacesFromType; + public RegisterTypeInIl2CppWithInterfaces(bool logSuccess) + { + LogSuccess = logSuccess; + GetInterfacesFromType = true; + } - public RegisterTypeInIl2CppWithInterfaces() - { - GetInterfacesFromType = true; - } + public RegisterTypeInIl2CppWithInterfaces(params Type[] interfaces) + { + Interfaces = interfaces; + } - public RegisterTypeInIl2CppWithInterfaces(bool logSuccess) - { - LogSuccess = logSuccess; - GetInterfacesFromType = true; - } + public RegisterTypeInIl2CppWithInterfaces(bool logSuccess, params Type[] interfaces) + { + LogSuccess = logSuccess; + Interfaces = interfaces; + } - public RegisterTypeInIl2CppWithInterfaces(params Type[] interfaces) - { - Interfaces = interfaces; - } + public static void RegisterAssembly(Assembly asm) + { + if (!MelonUtils.IsGameIl2Cpp()) + return; - public RegisterTypeInIl2CppWithInterfaces(bool logSuccess, params Type[] interfaces) + if (!ready) { - LogSuccess = logSuccess; - Interfaces = interfaces; + registrationQueue.Add(asm); + return; } - public static void RegisterAssembly(Assembly asm) - { - if (!MelonUtils.IsGameIl2Cpp()) - return; - - if (!ready) - { - registrationQueue.Add(asm); - return; - } - - IEnumerable typeTbl = asm.GetValidTypes(); - if ((typeTbl == null) || (typeTbl.Count() <= 0)) - return; - - foreach (Type type in typeTbl) - { - object[] attTbl = type.GetCustomAttributes(typeof(RegisterTypeInIl2CppWithInterfaces), false); - if ((attTbl == null) || (attTbl.Length <= 0)) - continue; - - RegisterTypeInIl2CppWithInterfaces att = (RegisterTypeInIl2CppWithInterfaces)attTbl[0]; - if (att == null) - continue; - - Type[] interfaceArr = att.GetInterfacesFromType - ? type.GetInterfaces() - : att.Interfaces; - - InteropSupport.RegisterTypeInIl2CppDomainWithInterfaces(type, - interfaceArr, - att.LogSuccess); - } - } + var typeTbl = asm.GetValidTypes(); + if ((typeTbl == null) || (typeTbl.Count() <= 0)) + return; - internal static void SetReady() + foreach (var type in typeTbl) { - ready = true; + var attTbl = type.GetCustomAttributes(typeof(RegisterTypeInIl2CppWithInterfaces), false); + if ((attTbl == null) || (attTbl.Length <= 0)) + continue; - if (registrationQueue == null) - return; + var att = (RegisterTypeInIl2CppWithInterfaces)attTbl[0]; + if (att == null) + continue; - foreach (var asm in registrationQueue) - RegisterAssembly(asm); + var interfaceArr = att.GetInterfacesFromType + ? type.GetInterfaces() + : att.Interfaces; - registrationQueue = null; + InteropSupport.RegisterTypeInIl2CppDomainWithInterfaces(type, + interfaceArr, + att.LogSuccess); } } + + internal static void SetReady() + { + ready = true; + + if (registrationQueue == null) + return; + + foreach (var asm in registrationQueue) + RegisterAssembly(asm); + + registrationQueue = null; + } } \ No newline at end of file diff --git a/MelonLoader/ResolvedMelons.cs b/MelonLoader/ResolvedMelons.cs index 326bb87c1..2afa81d00 100644 --- a/MelonLoader/ResolvedMelons.cs +++ b/MelonLoader/ResolvedMelons.cs @@ -1,14 +1,13 @@ -namespace MelonLoader +namespace MelonLoader; + +public sealed class ResolvedMelons // This class only exists because I can't use Tuples { - public sealed class ResolvedMelons // This class only exists because I can't use Tuples - { - public readonly MelonBase[] loadedMelons; - public readonly RottenMelon[] rottenMelons; + public readonly MelonBase[] loadedMelons; + public readonly RottenMelon[] rottenMelons; - public ResolvedMelons(MelonBase[] loadedMelons, RottenMelon[] rottenMelons) - { - this.loadedMelons = loadedMelons ?? new MelonBase[0]; - this.rottenMelons = rottenMelons ?? new RottenMelon[0]; - } + public ResolvedMelons(MelonBase[] loadedMelons, RottenMelon[] rottenMelons) + { + this.loadedMelons = loadedMelons ?? new MelonBase[0]; + this.rottenMelons = rottenMelons ?? new RottenMelon[0]; } } diff --git a/MelonLoader/Resolver/AssemblyManager.cs b/MelonLoader/Resolver/AssemblyManager.cs index e8025ddc0..470907257 100644 --- a/MelonLoader/Resolver/AssemblyManager.cs +++ b/MelonLoader/Resolver/AssemblyManager.cs @@ -8,90 +8,88 @@ #pragma warning disable CS8632 -namespace MelonLoader.Resolver +namespace MelonLoader.Resolver; + +internal class AssemblyManager { - internal class AssemblyManager - { - internal static Dictionary InfoDict = new Dictionary(); + internal static Dictionary InfoDict = []; - internal static bool Setup() - { - InstallHooks(); + internal static bool Setup() + { + InstallHooks(); - // Setup all Loaded Assemblies - foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) - LoadInfo(assembly); + // Setup all Loaded Assemblies + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + LoadInfo(assembly); - return true; - } + return true; + } - internal static AssemblyResolveInfo GetInfo(string name) - { - if (InfoDict.TryGetValue(name, out AssemblyResolveInfo resolveInfo)) - return resolveInfo; - lock (InfoDict) - InfoDict[name] = new AssemblyResolveInfo(); - return InfoDict[name]; - } + internal static AssemblyResolveInfo GetInfo(string name) + { + if (InfoDict.TryGetValue(name, out var resolveInfo)) + return resolveInfo; + lock (InfoDict) + InfoDict[name] = new AssemblyResolveInfo(); + return InfoDict[name]; + } - private static Assembly Resolve(string requested_name, Version requested_version, bool is_preload) - { - // Get Resolve Information Object - AssemblyResolveInfo resolveInfo = GetInfo(requested_name); + private static Assembly Resolve(string requested_name, Version requested_version, bool is_preload) + { + // Get Resolve Information Object + var resolveInfo = GetInfo(requested_name); - // Resolve the Information Object - Assembly assembly = resolveInfo.Resolve(requested_version); + // Resolve the Information Object + var assembly = resolveInfo.Resolve(requested_version); - // Run Passthrough Events - if (assembly == null) - assembly = MelonAssemblyResolver.SafeInvoke_OnAssemblyResolve(requested_name, requested_version); + // Run Passthrough Events + assembly ??= MelonAssemblyResolver.SafeInvoke_OnAssemblyResolve(requested_name, requested_version); - // Search Directories - if (is_preload && (assembly == null)) - assembly = SearchDirectoryManager.Scan(requested_name); + // Search Directories + if (is_preload && (assembly == null)) + assembly = SearchDirectoryManager.Scan(requested_name); - // Load if Valid Assembly - if (assembly != null) - LoadInfo(assembly); + // Load if Valid Assembly + if (assembly != null) + LoadInfo(assembly); - // Return - return assembly; - } + // Return + return assembly; + } - internal static void LoadInfo(Assembly assembly) - { - // Get AssemblyName - AssemblyName assemblyName = assembly.GetName(); + internal static void LoadInfo(Assembly assembly) + { + // Get AssemblyName + var assemblyName = assembly.GetName(); - // Get Resolve Information Object - AssemblyResolveInfo resolveInfo = GetInfo(assemblyName.Name); + // Get Resolve Information Object + var resolveInfo = GetInfo(assemblyName.Name); - // Set Version of Assembly - resolveInfo.SetVersionSpecific(assemblyName.Version, assembly); + // Set Version of Assembly + resolveInfo.SetVersionSpecific(assemblyName.Version, assembly); - // Run Passthrough Events - MelonAssemblyResolver.SafeInvoke_OnAssemblyLoad(assembly); - } + // Run Passthrough Events + MelonAssemblyResolver.SafeInvoke_OnAssemblyLoad(assembly); + } - private static void InstallHooks() - { + private static void InstallHooks() + { #if NET6_0_OR_GREATER - AssemblyLoadContext.Default.Resolving += Resolve; + AssemblyLoadContext.Default.Resolving += Resolve; #else - InternalUtils.BootstrapInterop.Library.MonoInstallHooks(); + InternalUtils.BootstrapInterop.Library.MonoInstallHooks(); #endif - } + } #if NET6_0_OR_GREATER - private static Assembly? Resolve(AssemblyLoadContext alc, AssemblyName name) - => Resolve(name.Name, name.Version, true); + private static Assembly? Resolve(AssemblyLoadContext alc, AssemblyName name) + => Resolve(name.Name, name.Version, true); #else - private static Assembly Resolve(string requested_name, ushort major, ushort minor, ushort build, ushort revision, bool is_preload) - { - Version requested_version = new Version(major, minor, build, revision); - return Resolve(requested_name, requested_version, is_preload); - } -#endif + private static Assembly Resolve(string requested_name, ushort major, ushort minor, ushort build, ushort revision, bool is_preload) + { + var requested_version = new Version(major, minor, build, revision); + return Resolve(requested_name, requested_version, is_preload); } +#endif } diff --git a/MelonLoader/Resolver/AssemblyResolveInfo.cs b/MelonLoader/Resolver/AssemblyResolveInfo.cs index eb975cfbc..a2cf511f8 100644 --- a/MelonLoader/Resolver/AssemblyResolveInfo.cs +++ b/MelonLoader/Resolver/AssemblyResolveInfo.cs @@ -2,42 +2,38 @@ using System.Collections.Generic; using System.Reflection; -namespace MelonLoader.Resolver -{ - public class AssemblyResolveInfo - { - public Assembly Override = null; - public Assembly Fallback = null; - internal Dictionary Versions = new Dictionary(); +namespace MelonLoader.Resolver; - internal Assembly Resolve(Version requested_version) - { - // Check for Override - if (Override != null) - return Override; +public class AssemblyResolveInfo +{ + public Assembly Override = null; + public Assembly Fallback = null; + internal Dictionary Versions = []; - // Check for Requested Version - if (requested_version != null - && GetVersionSpecific(requested_version, out Assembly assembly)) - return assembly; + internal Assembly Resolve(Version requested_version) + { + // Check for Override + if (Override != null) + return Override; - // Check for Fallback - if (Fallback != null) - return Fallback; + // Check for Requested Version + if (requested_version != null + && GetVersionSpecific(requested_version, out var assembly)) + return assembly; - return null; - } + // Check for Fallback + return Fallback != null ? Fallback : null; + } - public void SetVersionSpecific(Version version, Assembly assembly = null) - { - lock (Versions) - Versions[version] = assembly; - } - public Assembly GetVersionSpecific(Version version) - => GetVersionSpecific(version, out Assembly assembly) - ? assembly - : null; - public bool GetVersionSpecific(Version version, out Assembly assembly) - => Versions.TryGetValue(version, out assembly) && assembly != null; + public void SetVersionSpecific(Version version, Assembly assembly = null) + { + lock (Versions) + Versions[version] = assembly; } + public Assembly GetVersionSpecific(Version version) + => GetVersionSpecific(version, out var assembly) + ? assembly + : null; + public bool GetVersionSpecific(Version version, out Assembly assembly) + => Versions.TryGetValue(version, out assembly) && assembly != null; } diff --git a/MelonLoader/Resolver/MelonAssemblyResolver.cs b/MelonLoader/Resolver/MelonAssemblyResolver.cs index 32636cee8..1595538b3 100644 --- a/MelonLoader/Resolver/MelonAssemblyResolver.cs +++ b/MelonLoader/Resolver/MelonAssemblyResolver.cs @@ -1,7 +1,7 @@ -using System; +using MelonLoader.Utils; +using System; using System.IO; using System.Reflection; -using MelonLoader.Utils; #if NET6_0_OR_GREATER using System.Runtime.Loader; @@ -9,133 +9,134 @@ #pragma warning disable CS0618 // Type or member is obsolete -namespace MelonLoader.Resolver +namespace MelonLoader.Resolver; + +public class MelonAssemblyResolver { - public class MelonAssemblyResolver + internal static void Setup() { - internal static void Setup() - { - if (!AssemblyManager.Setup()) - return; + if (!AssemblyManager.Setup()) + return; + + // Setup Search Directories + AddSearchDirectories( + MelonEnvironment.UserLibsDirectory, + MelonEnvironment.PluginsDirectory, + MelonEnvironment.ModsDirectory, + MelonUtils.IsGameIl2Cpp() + ? MelonEnvironment.Il2CppAssembliesDirectory + : MelonEnvironment.UnityGameManagedDirectory, + MelonEnvironment.OurRuntimeDirectory, + MelonEnvironment.MelonBaseDirectory, + MelonEnvironment.GameRootDirectory); + + // Setup Redirections + OverrideBaseAssembly(); + + // Resolve Default Runtime Assemblies + ForceResolveRuntime( + "Mono.Cecil.dll", + "MonoMod.exe", + "MonoMod.Utils.dll", + "MonoMod.RuntimeDetour.dll"); + + MelonDebug.Msg("[MelonAssemblyResolver] Setup Successful!"); + } - // Setup Search Directories - AddSearchDirectories( - MelonEnvironment.UserLibsDirectory, - MelonEnvironment.PluginsDirectory, - MelonEnvironment.ModsDirectory, - (MelonUtils.IsGameIl2Cpp() - ? MelonEnvironment.Il2CppAssembliesDirectory - : MelonEnvironment.UnityGameManagedDirectory), - MelonEnvironment.OurRuntimeDirectory, - MelonEnvironment.MelonBaseDirectory, - MelonEnvironment.GameRootDirectory); - - // Setup Redirections - OverrideBaseAssembly(); - - // Resolve Default Runtime Assemblies - ForceResolveRuntime( - "Mono.Cecil.dll", - "MonoMod.exe", - "MonoMod.Utils.dll", - "MonoMod.RuntimeDetour.dll"); - - MelonDebug.Msg("[MelonAssemblyResolver] Setup Successful!"); - } + private static void OverrideBaseAssembly() + { + var base_assembly = typeof(MelonAssemblyResolver).Assembly; + GetAssemblyResolveInfo(base_assembly.GetName().Name).Override = base_assembly; + GetAssemblyResolveInfo("MelonLoader").Override = base_assembly; + GetAssemblyResolveInfo("MelonLoader.ModHandler").Override = base_assembly; + } - private static void OverrideBaseAssembly() + private static void ForceResolveRuntime(params string[] fileNames) + { + foreach (var fileName in fileNames) { - Assembly base_assembly = typeof(MelonAssemblyResolver).Assembly; - GetAssemblyResolveInfo(base_assembly.GetName().Name).Override = base_assembly; - GetAssemblyResolveInfo("MelonLoader").Override = base_assembly; - GetAssemblyResolveInfo("MelonLoader.ModHandler").Override = base_assembly; - } + var filePath = Path.Combine(MelonEnvironment.OurRuntimeDirectory, fileName); + if (!File.Exists(filePath)) + return; - private static void ForceResolveRuntime(params string[] fileNames) - { - foreach (string fileName in fileNames) + Assembly assembly = null; + try { - string filePath = Path.Combine(MelonEnvironment.OurRuntimeDirectory, fileName); - if (!File.Exists(filePath)) - return; - - Assembly assembly = null; - try - { #if NET6_0_OR_GREATER - assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(filePath); + assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(filePath); #else assembly = Assembly.LoadFrom(filePath); #endif - } - catch { assembly = null; } + } + catch + { + assembly = null; + } - if (assembly == null) - return; + if (assembly == null) + return; - GetAssemblyResolveInfo(Path.GetFileNameWithoutExtension(fileName)).Override = assembly; - } + GetAssemblyResolveInfo(Path.GetFileNameWithoutExtension(fileName)).Override = assembly; } + } - // Search Directories + // Search Directories - public static void AddSearchDirectories(params string[] directories) - { - foreach (string directory in directories) - AddSearchDirectory(directory); - } + public static void AddSearchDirectories(params string[] directories) + { + foreach (var directory in directories) + AddSearchDirectory(directory); + } - public static void AddSearchDirectories(int priority, params string[] directories) - { - foreach (string directory in directories) - AddSearchDirectory(directory, priority); - } + public static void AddSearchDirectories(int priority, params string[] directories) + { + foreach (var directory in directories) + AddSearchDirectory(directory, priority); + } - public static void AddSearchDirectories(params (string, int)[] directories) - { - foreach (var pair in directories) - AddSearchDirectory(pair.Item1, pair.Item2); - } + public static void AddSearchDirectories(params (string, int)[] directories) + { + foreach (var pair in directories) + AddSearchDirectory(pair.Item1, pair.Item2); + } - public static void AddSearchDirectory(string path, int priority = 0) - => SearchDirectoryManager.Add(path, priority); - public static void RemoveSearchDirectory(string path) - => SearchDirectoryManager.Remove(path); + public static void AddSearchDirectory(string path, int priority = 0) + => SearchDirectoryManager.Add(path, priority); + public static void RemoveSearchDirectory(string path) + => SearchDirectoryManager.Remove(path); - // Assembly - public delegate void OnAssemblyLoadHandler(Assembly assembly); - public static event OnAssemblyLoadHandler OnAssemblyLoad; - internal static void SafeInvoke_OnAssemblyLoad(Assembly assembly) - { + // Assembly + public delegate void OnAssemblyLoadHandler(Assembly assembly); + public static event OnAssemblyLoadHandler OnAssemblyLoad; + internal static void SafeInvoke_OnAssemblyLoad(Assembly assembly) + { #if !NET6_0_OR_GREATER - // Backwards Compatibility - MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyLoad(assembly); + // Backwards Compatibility + MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyLoad(assembly); #endif - OnAssemblyLoad?.Invoke(assembly); - } + OnAssemblyLoad?.Invoke(assembly); + } - public delegate Assembly OnAssemblyResolveHandler(string name, Version version); - public static event OnAssemblyResolveHandler OnAssemblyResolve; - internal static Assembly SafeInvoke_OnAssemblyResolve(string name, Version version) - { + public delegate Assembly OnAssemblyResolveHandler(string name, Version version); + public static event OnAssemblyResolveHandler OnAssemblyResolve; + internal static Assembly SafeInvoke_OnAssemblyResolve(string name, Version version) + { #if NET6_0_OR_GREATER - return OnAssemblyResolve?.Invoke(name, version); + return OnAssemblyResolve?.Invoke(name, version); #else - // Backwards Compatibility - var assembly = MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyResolve(name, version); - if (assembly == null) - assembly = OnAssemblyResolve?.Invoke(name, version); - return assembly; + // Backwards Compatibility + var assembly = MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyResolve(name, version); + assembly ??= OnAssemblyResolve?.Invoke(name, version); + return assembly; #endif - } - - public static AssemblyResolveInfo GetAssemblyResolveInfo(string name) - => AssemblyManager.GetInfo(name); - public static void LoadInfoFromAssembly(Assembly assembly) - => AssemblyManager.LoadInfo(assembly); } + + public static AssemblyResolveInfo GetAssemblyResolveInfo(string name) + => AssemblyManager.GetInfo(name); + public static void LoadInfoFromAssembly(Assembly assembly) + => AssemblyManager.LoadInfo(assembly); } diff --git a/MelonLoader/Resolver/SearchDirectoryManager.cs b/MelonLoader/Resolver/SearchDirectoryManager.cs index 83d4e4150..e86d36082 100644 --- a/MelonLoader/Resolver/SearchDirectoryManager.cs +++ b/MelonLoader/Resolver/SearchDirectoryManager.cs @@ -11,111 +11,112 @@ using MelonLoader.Utils; #endif -namespace MelonLoader.Resolver +namespace MelonLoader.Resolver; + +internal static class SearchDirectoryManager { - internal static class SearchDirectoryManager + private static List SearchDirectoryList = []; + + private static void Sort() + => SearchDirectoryList = + SearchDirectoryList.OrderBy(x => x.Priority).ToList(); + + internal static void Add(string path, int priority = 0) { - private static List SearchDirectoryList = new List(); + if (string.IsNullOrEmpty(path)) + return; - private static void Sort() - => SearchDirectoryList = - SearchDirectoryList.OrderBy(x => x.Priority).ToList(); + path = Path.GetFullPath(path); + if (path.ContainsExtension()) + return; - internal static void Add(string path, int priority = 0) + var searchDirectory = SearchDirectoryList.FirstOrDefault(x => x.Path.Equals(path)); + if (searchDirectory != null) + return; + + searchDirectory = new SearchDirectoryInfo { - if (string.IsNullOrEmpty(path)) - return; + Path = path, + Priority = priority + }; + SearchDirectoryList.Add(searchDirectory); - path = Path.GetFullPath(path); - if (path.ContainsExtension()) - return; + Sort(); + } - SearchDirectoryInfo searchDirectory = SearchDirectoryList.FirstOrDefault(x => x.Path.Equals(path)); - if (searchDirectory != null) - return; + internal static void Remove(string path) + { + if (string.IsNullOrEmpty(path)) + return; - searchDirectory = new SearchDirectoryInfo(); - searchDirectory.Path = path; - searchDirectory.Priority = priority; - SearchDirectoryList.Add(searchDirectory); + path = Path.GetFullPath(path); + if (path.ContainsExtension()) + return; - Sort(); - } + var searchDirectory = SearchDirectoryList.FirstOrDefault(x => x.Path.Equals(path)); + if (searchDirectory == null) + return; - internal static void Remove(string path) - { - if (string.IsNullOrEmpty(path)) - return; + SearchDirectoryList.Remove(searchDirectory); - path = Path.GetFullPath(path); - if (path.ContainsExtension()) - return; + Sort(); + } - SearchDirectoryInfo searchDirectory = SearchDirectoryList.FirstOrDefault(x => x.Path.Equals(path)); - if (searchDirectory == null) - return; + internal static Assembly Scan(string requested_name) + { + var enumerator = new LemonEnumerator(SearchDirectoryList); + while (enumerator.MoveNext()) + { + var folderpath = enumerator.Current.Path; + if (folderpath.ContainsExtension() + || !Directory.Exists(folderpath)) + continue; - SearchDirectoryList.Remove(searchDirectory); + var filepath = Directory.GetFiles(folderpath).Where(x => + !string.IsNullOrEmpty(x) + && ((Path.GetExtension(x).ToLowerInvariant().Equals(".dll") + && Path.GetFileName(x).Equals($"{requested_name}.dll")) + || (Path.GetExtension(x).ToLowerInvariant().Equals(".exe") + && Path.GetFileName(x).Equals($"{requested_name}.exe"))) + ).FirstOrDefault(); - Sort(); - } + if (string.IsNullOrEmpty(filepath)) + continue; - internal static Assembly Scan(string requested_name) - { - LemonEnumerator enumerator = new LemonEnumerator(SearchDirectoryList); - while (enumerator.MoveNext()) - { - string folderpath = enumerator.Current.Path; - if (folderpath.ContainsExtension() - || !Directory.Exists(folderpath)) - continue; - - string filepath = Directory.GetFiles(folderpath).Where(x => - (!string.IsNullOrEmpty(x) - && ((Path.GetExtension(x).ToLowerInvariant().Equals(".dll") - && Path.GetFileName(x).Equals($"{requested_name}.dll")) - || (Path.GetExtension(x).ToLowerInvariant().Equals(".exe") - && Path.GetFileName(x).Equals($"{requested_name}.exe")))) - ).FirstOrDefault(); - - if (string.IsNullOrEmpty(filepath)) - continue; - - MelonDebug.Msg($"[MelonAssemblyResolver] Loading from {filepath}..."); + MelonDebug.Msg($"[MelonAssemblyResolver] Loading from {filepath}..."); #if NET6_0_OR_GREATER - return AssemblyLoadContext.Default.LoadFromAssemblyPath(filepath); + return AssemblyLoadContext.Default.LoadFromAssemblyPath(filepath); #else - IntPtr filePathPtr = Marshal.StringToHGlobalAnsi(filepath); - if (filePathPtr == IntPtr.Zero) - continue; + var filePathPtr = Marshal.StringToHGlobalAnsi(filepath); + if (filePathPtr == IntPtr.Zero) + continue; - IntPtr rootPtr = InternalUtils.BootstrapInterop.Library.MonoGetDomainPtr(); - if (rootPtr == IntPtr.Zero) - continue; + IntPtr rootPtr = InternalUtils.BootstrapInterop.Library.MonoGetDomainPtr(); + if (rootPtr == IntPtr.Zero) + continue; - IntPtr assemblyPtr = MonoLibrary.Instance.mono_assembly_open_full(filePathPtr, IntPtr.Zero, false); - if (assemblyPtr == IntPtr.Zero) - continue; + var assemblyPtr = MonoLibrary.Instance.mono_assembly_open_full(filePathPtr, IntPtr.Zero, false); + if (assemblyPtr == IntPtr.Zero) + continue; - IntPtr assemblyReflectionPtr = MonoLibrary.Instance.mono_assembly_get_object(rootPtr, assemblyPtr); - if (assemblyReflectionPtr == IntPtr.Zero) - continue; + var assemblyReflectionPtr = MonoLibrary.Instance.mono_assembly_get_object(rootPtr, assemblyPtr); + if (assemblyReflectionPtr == IntPtr.Zero) + continue; - return MonoLibrary.CastManagedAssemblyPtr(assemblyReflectionPtr); + return MonoLibrary.CastManagedAssemblyPtr(assemblyReflectionPtr); #endif - } - - MelonDebug.Msg($"[MelonAssemblyResolver] Failed to find {requested_name} in any of the known search directories"); - return null; } - private class SearchDirectoryInfo - { - internal string Path = null; - internal int Priority = 0; - } + MelonDebug.Msg($"[MelonAssemblyResolver] Failed to find {requested_name} in any of the known search directories"); + return null; + } + + private class SearchDirectoryInfo + { + internal string Path = null; + internal int Priority = 0; } } diff --git a/MelonLoader/RottenMelon.cs b/MelonLoader/RottenMelon.cs index 39e9d3c5d..62753c554 100644 --- a/MelonLoader/RottenMelon.cs +++ b/MelonLoader/RottenMelon.cs @@ -1,23 +1,22 @@ using System; -namespace MelonLoader +namespace MelonLoader; + +/// +/// An info class for broken Melons. +/// +public sealed class RottenMelon { - /// - /// An info class for broken Melons. - /// - public sealed class RottenMelon - { - public readonly MelonAssembly assembly; - public readonly Type type; - public readonly string errorMessage; - public readonly Exception exception; + public readonly MelonAssembly assembly; + public readonly Type type; + public readonly string errorMessage; + public readonly Exception exception; - public RottenMelon(Type type, string errorMessage, Exception exception = null) - { - assembly = MelonAssembly.LoadMelonAssembly(null, type.Assembly); - this.type = type; - this.errorMessage = errorMessage; - this.exception = exception; - } + public RottenMelon(Type type, string errorMessage, Exception exception = null) + { + assembly = MelonAssembly.LoadMelonAssembly(null, type.Assembly); + this.type = type; + this.errorMessage = errorMessage; + this.exception = exception; } } diff --git a/MelonLoader/Semver/IntExtensions.cs b/MelonLoader/Semver/IntExtensions.cs index 691cf0a8d..9fe922297 100644 --- a/MelonLoader/Semver/IntExtensions.cs +++ b/MelonLoader/Semver/IntExtensions.cs @@ -1,35 +1,41 @@ using System.Text; #pragma warning disable IDE0130 // Namespace does not match folder structure -namespace Semver +namespace Semver; #pragma warning restore IDE0130 // Namespace does not match folder structure + +internal static class IntExtensions { - internal static class IntExtensions + /// + /// The number of digits in a non-negative number. Returns 1 for all + /// negative numbers. That is ok because we are using it to calculate + /// string length for a for numbers that + /// aren't supposed to be negative, but when they are it is just a little + /// slower. + /// + /// + /// This approach is based on https://stackoverflow.com/a/51099524/268898 + /// where the poster offers performance benchmarks showing this is the + /// fastest way to get a number of digits. + /// + public static int Digits(this int n) { - /// - /// The number of digits in a non-negative number. Returns 1 for all - /// negative numbers. That is ok because we are using it to calculate - /// string length for a for numbers that - /// aren't supposed to be negative, but when they are it is just a little - /// slower. - /// - /// - /// This approach is based on https://stackoverflow.com/a/51099524/268898 - /// where the poster offers performance benchmarks showing this is the - /// fastest way to get a number of digits. - /// - public static int Digits(this int n) - { - if (n < 10) return 1; - if (n < 100) return 2; - if (n < 1_000) return 3; - if (n < 10_000) return 4; - if (n < 100_000) return 5; - if (n < 1_000_000) return 6; - if (n < 10_000_000) return 7; - if (n < 100_000_000) return 8; - if (n < 1_000_000_000) return 9; - return 10; - } + if (n < 10) + return 1; + if (n < 100) + return 2; + if (n < 1_000) + return 3; + if (n < 10_000) + return 4; + if (n < 100_000) + return 5; + if (n < 1_000_000) + return 6; + if (n < 10_000_000) + return 7; + if (n < 100_000_000) + return 8; + return n < 1_000_000_000 ? 9 : 10; } } diff --git a/MelonLoader/Semver/SemVersion.cs b/MelonLoader/Semver/SemVersion.cs index ea64c9ef2..e3311e176 100644 --- a/MelonLoader/Semver/SemVersion.cs +++ b/MelonLoader/Semver/SemVersion.cs @@ -3,563 +3,567 @@ using System.Text; #if !NETSTANDARD using System.Runtime.Serialization; -using System.Security.Permissions; #endif using System.Text.RegularExpressions; #pragma warning disable IDE0130 // Namespace does not match folder structure -namespace Semver +namespace Semver; #pragma warning restore IDE0130 // Namespace does not match folder structure -{ - /// - /// A semantic version implementation. - /// Conforms with v2.0.0 of http://semver.org - /// + +/// +/// A semantic version implementation. +/// Conforms with v2.0.0 of http://semver.org +/// #if NETSTANDARD - public sealed class SemVersion : IComparable, IComparable +public sealed class SemVersion : IComparable, IComparable #else - [Serializable] - public sealed class SemVersion : IComparable, IComparable, ISerializable +[Serializable] +public sealed class SemVersion : IComparable, IComparable, ISerializable #endif - { - private static readonly Regex ParseEx = - new(@"^(?\d+)" + - @"(?>\.(?\d+))?" + - @"(?>\.(?\d+))?" + - @"(?>\-(?
[0-9A-Za-z\-\.]+))?" +
-                @"(?>\+(?[0-9A-Za-z\-\.]+))?$",
-                RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
+{
+    private static readonly Regex ParseEx =
+        new(@"^(?\d+)" +
+            @"(?>\.(?\d+))?" +
+            @"(?>\.(?\d+))?" +
+            @"(?>\-(?
[0-9A-Za-z\-\.]+))?" +
+            @"(?>\+(?[0-9A-Za-z\-\.]+))?$",
+            RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
 
 #if !NETSTANDARD
-        /// 
-        /// Deserialize a .
-        /// 
-        /// The  parameter is null.
-        private SemVersion(SerializationInfo info, StreamingContext context)
-        {
-            if (info == null) throw new ArgumentNullException(nameof(info));
-            var semVersion = Parse(info.GetString("SemVersion"));
-            Major = semVersion.Major;
-            Minor = semVersion.Minor;
-            Patch = semVersion.Patch;
-            Prerelease = semVersion.Prerelease;
-            Build = semVersion.Build;
-        }
+    /// 
+    /// Deserialize a .
+    /// 
+    /// The  parameter is null.
+    private SemVersion(SerializationInfo info, StreamingContext context)
+    {
+        if (info == null)
+            throw new ArgumentNullException(nameof(info));
+        var semVersion = Parse(info.GetString("SemVersion"));
+        Major = semVersion.Major;
+        Minor = semVersion.Minor;
+        Patch = semVersion.Patch;
+        Prerelease = semVersion.Prerelease;
+        Build = semVersion.Build;
+    }
 #endif
 
-        /// 
-        /// Constructs a new instance of the  class.
-        /// 
-        /// The major version.
-        /// The minor version.
-        /// The patch version.
-        /// The prerelease version (e.g. "alpha").
-        /// The build metadata (e.g. "nightly.232").
-        public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
-        {
-            Major = major;
-            Minor = minor;
-            Patch = patch;
+    /// 
+    /// Constructs a new instance of the  class.
+    /// 
+    /// The major version.
+    /// The minor version.
+    /// The patch version.
+    /// The prerelease version (e.g. "alpha").
+    /// The build metadata (e.g. "nightly.232").
+    public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
+    {
+        Major = major;
+        Minor = minor;
+        Patch = patch;
 
-            Prerelease = prerelease ?? "";
-            Build = build ?? "";
-        }
+        Prerelease = prerelease ?? "";
+        Build = build ?? "";
+    }
 
-        /// 
-        /// Constructs a new instance of the  class from
-        /// a .
-        /// 
-        /// The  that is used to initialize
-        /// the Major, Minor, Patch and Build.
-        /// A  with the same Major and Minor version.
-        /// The Patch version will be the fourth part of the version number. The
-        /// build meta data will contain the third part of the version number if
-        /// it is greater than zero.
-        public SemVersion(Version version)
-        {
-            if (version == null)
-                throw new ArgumentNullException(nameof(version));
+    /// 
+    /// Constructs a new instance of the  class from
+    /// a .
+    /// 
+    /// The  that is used to initialize
+    /// the Major, Minor, Patch and Build.
+    /// A  with the same Major and Minor version.
+    /// The Patch version will be the fourth part of the version number. The
+    /// build meta data will contain the third part of the version number if
+    /// it is greater than zero.
+    public SemVersion(Version version)
+    {
+        if (version == null)
+            throw new ArgumentNullException(nameof(version));
 
-            Major = version.Major;
-            Minor = version.Minor;
+        Major = version.Major;
+        Minor = version.Minor;
 
-            if (version.Revision >= 0)
-                Patch = version.Revision;
+        if (version.Revision >= 0)
+            Patch = version.Revision;
 
-            Prerelease = "";
+        Prerelease = "";
 
-            Build = version.Build > 0 ? version.Build.ToString(CultureInfo.InvariantCulture) : "";
-        }
+        Build = version.Build > 0 ? version.Build.ToString(CultureInfo.InvariantCulture) : "";
+    }
+
+    /// 
+    /// Converts the string representation of a semantic version to its  equivalent.
+    /// 
+    /// The version string.
+    /// If set to  minor and patch version are required,
+    /// otherwise they are optional.
+    /// The  object.
+    /// The  is .
+    /// The  has an invalid format.
+    /// The  is missing Minor or Patch versions and  is .
+    /// The Major, Minor, or Patch versions are larger than int.MaxValue.
+    public static SemVersion Parse(string version, bool strict = false)
+    {
+        var match = ParseEx.Match(version);
+        if (!match.Success)
+            throw new ArgumentException($"Invalid version '{version}'.", nameof(version));
+
+        var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
+
+        var minorMatch = match.Groups["minor"];
+        var minor = 0;
+        if (minorMatch.Success)
+            minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
+        else if (strict)
+            throw new InvalidOperationException("Invalid version (no minor version given in strict mode)");
+
+        var patchMatch = match.Groups["patch"];
+        var patch = 0;
+        if (patchMatch.Success)
+            patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
+        else if (strict)
+            throw new InvalidOperationException("Invalid version (no patch version given in strict mode)");
+
+        var prerelease = match.Groups["pre"].Value;
+        var build = match.Groups["build"].Value;
+
+        return new SemVersion(major, minor, patch, prerelease, build);
+    }
+
+    /// 
+    /// Converts the string representation of a semantic version to its 
+    /// equivalent and returns a value that indicates whether the conversion succeeded.
+    /// 
+    /// The version string.
+    /// When the method returns, contains a  instance equivalent
+    /// to the version string passed in, if the version string was valid, or  if the
+    /// version string was not valid.
+    /// If set to  minor and patch version are required,
+    /// otherwise they are optional.
+    ///  when a invalid version string is passed, otherwise .
+    public static bool TryParse(string version, out SemVersion semver, bool strict = false)
+    {
+        semver = null;
+        if (version is null)
+            return false;
+
+        var match = ParseEx.Match(version);
+        if (!match.Success)
+            return false;
 
-        /// 
-        /// Converts the string representation of a semantic version to its  equivalent.
-        /// 
-        /// The version string.
-        /// If set to  minor and patch version are required,
-        /// otherwise they are optional.
-        /// The  object.
-        /// The  is .
-        /// The  has an invalid format.
-        /// The  is missing Minor or Patch versions and  is .
-        /// The Major, Minor, or Patch versions are larger than int.MaxValue.
-        public static SemVersion Parse(string version, bool strict = false)
+        if (!int.TryParse(match.Groups["major"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var major))
+            return false;
+
+        var minorMatch = match.Groups["minor"];
+        var minor = 0;
+        if (minorMatch.Success)
         {
-            var match = ParseEx.Match(version);
-            if (!match.Success)
-                throw new ArgumentException($"Invalid version '{version}'.", nameof(version));
-
-            var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
-
-            var minorMatch = match.Groups["minor"];
-            int minor = 0;
-            if (minorMatch.Success)
-                minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
-            else if (strict)
-                throw new InvalidOperationException("Invalid version (no minor version given in strict mode)");
-
-            var patchMatch = match.Groups["patch"];
-            int patch = 0;
-            if (patchMatch.Success)
-                patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
-            else if (strict)
-                throw new InvalidOperationException("Invalid version (no patch version given in strict mode)");
-
-            var prerelease = match.Groups["pre"].Value;
-            var build = match.Groups["build"].Value;
-
-            return new SemVersion(major, minor, patch, prerelease, build);
+            if (!int.TryParse(minorMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out minor))
+                return false;
         }
+        else if (strict)
+            return false;
 
-        /// 
-        /// Converts the string representation of a semantic version to its 
-        /// equivalent and returns a value that indicates whether the conversion succeeded.
-        /// 
-        /// The version string.
-        /// When the method returns, contains a  instance equivalent
-        /// to the version string passed in, if the version string was valid, or  if the
-        /// version string was not valid.
-        /// If set to  minor and patch version are required,
-        /// otherwise they are optional.
-        ///  when a invalid version string is passed, otherwise .
-        public static bool TryParse(string version, out SemVersion semver, bool strict = false)
+        var patchMatch = match.Groups["patch"];
+        var patch = 0;
+        if (patchMatch.Success)
         {
-            semver = null;
-            if (version is null) return false;
+            if (!int.TryParse(patchMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out patch))
+                return false;
+        }
+        else if (strict)
+            return false;
 
-            var match = ParseEx.Match(version);
-            if (!match.Success) return false;
+        var prerelease = match.Groups["pre"].Value;
+        var build = match.Groups["build"].Value;
 
-            if (!int.TryParse(match.Groups["major"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var major))
-                return false;
+        semver = new SemVersion(major, minor, patch, prerelease, build);
+        return true;
+    }
 
-            var minorMatch = match.Groups["minor"];
-            int minor = 0;
-            if (minorMatch.Success)
-            {
-                if (!int.TryParse(minorMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out minor))
-                    return false;
-            }
-            else if (strict) return false;
+    /// 
+    /// Checks whether two semantic versions are equal.
+    /// 
+    /// The first version to compare.
+    /// The second version to compare.
+    ///  if the two values are equal, otherwise .
+    public static bool Equals(SemVersion versionA, SemVersion versionB)
+    {
+        if (ReferenceEquals(versionA, versionB))
+            return true;
+        return versionA is not null && versionB is not null && versionA.Equals(versionB);
+    }
 
-            var patchMatch = match.Groups["patch"];
-            int patch = 0;
-            if (patchMatch.Success)
-            {
-                if (!int.TryParse(patchMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out patch))
-                    return false;
-            }
-            else if (strict) return false;
+    /// 
+    /// Compares the specified versions.
+    /// 
+    /// The first version to compare.
+    /// The second version to compare.
+    /// A signed number indicating the relative values of  and .
+    public static int Compare(SemVersion versionA, SemVersion versionB)
+    {
+        if (ReferenceEquals(versionA, versionB))
+            return 0;
+        if (versionA is null)
+            return -1;
+        return versionB is null ? 1 : versionA.CompareTo(versionB);
+    }
 
-            var prerelease = match.Groups["pre"].Value;
-            var build = match.Groups["build"].Value;
+    /// 
+    /// Make a copy of the current instance with changed properties.
+    /// 
+    /// The value to replace the major version or  to leave it unchanged.
+    /// The value to replace the minor version or  to leave it unchanged.
+    /// The value to replace the patch version or  to leave it unchanged.
+    /// The value to replace the prerelease version or  to leave it unchanged.
+    /// The value to replace the build metadata or  to leave it unchanged.
+    /// The new version object.
+    /// 
+    /// The change method is intended to be called using named argument syntax, passing only
+    /// those fields to be changed.
+    /// 
+    /// 
+    /// To change only the patch version:
+    /// version.Change(patch: 4)
+    /// 
+    public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
+        string prerelease = null, string build = null)
+    {
+        return new SemVersion(
+            major ?? Major,
+            minor ?? Minor,
+            patch ?? Patch,
+            prerelease ?? Prerelease,
+            build ?? Build);
+    }
 
-            semver = new SemVersion(major, minor, patch, prerelease, build);
-            return true;
-        }
+    /// 
+    /// Gets the major version.
+    /// 
+    /// 
+    /// The major version.
+    /// 
+    public int Major { get; }
 
-        /// 
-        /// Checks whether two semantic versions are equal.
-        /// 
-        /// The first version to compare.
-        /// The second version to compare.
-        ///  if the two values are equal, otherwise .
-        public static bool Equals(SemVersion versionA, SemVersion versionB)
-        {
-            if (ReferenceEquals(versionA, versionB)) return true;
-            if (versionA is null || versionB is null) return false;
-            return versionA.Equals(versionB);
-        }
+    /// 
+    /// Gets the minor version.
+    /// 
+    /// 
+    /// The minor version.
+    /// 
+    public int Minor { get; }
 
-        /// 
-        /// Compares the specified versions.
-        /// 
-        /// The first version to compare.
-        /// The second version to compare.
-        /// A signed number indicating the relative values of  and .
-        public static int Compare(SemVersion versionA, SemVersion versionB)
-        {
-            if (ReferenceEquals(versionA, versionB)) return 0;
-            if (versionA is null) return -1;
-            if (versionB is null) return 1;
-            return versionA.CompareTo(versionB);
-        }
+    /// 
+    /// Gets the patch version.
+    /// 
+    /// 
+    /// The patch version.
+    /// 
+    public int Patch { get; }
 
-        /// 
-        /// Make a copy of the current instance with changed properties.
-        /// 
-        /// The value to replace the major version or  to leave it unchanged.
-        /// The value to replace the minor version or  to leave it unchanged.
-        /// The value to replace the patch version or  to leave it unchanged.
-        /// The value to replace the prerelease version or  to leave it unchanged.
-        /// The value to replace the build metadata or  to leave it unchanged.
-        /// The new version object.
-        /// 
-        /// The change method is intended to be called using named argument syntax, passing only
-        /// those fields to be changed.
-        /// 
-        /// 
-        /// To change only the patch version:
-        /// version.Change(patch: 4)
-        /// 
-        public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
-            string prerelease = null, string build = null)
-        {
-            return new SemVersion(
-                major ?? Major,
-                minor ?? Minor,
-                patch ?? Patch,
-                prerelease ?? Prerelease,
-                build ?? Build);
-        }
+    /// 
+    /// Gets the prerelease version.
+    /// 
+    /// 
+    /// The prerelease version. Empty string if this is a release version.
+    /// 
+    public string Prerelease { get; }
 
-        /// 
-        /// Gets the major version.
-        /// 
-        /// 
-        /// The major version.
-        /// 
-        public int Major { get; }
-
-        /// 
-        /// Gets the minor version.
-        /// 
-        /// 
-        /// The minor version.
-        /// 
-        public int Minor { get; }
-
-        /// 
-        /// Gets the patch version.
-        /// 
-        /// 
-        /// The patch version.
-        /// 
-        public int Patch { get; }
-
-        /// 
-        /// Gets the prerelease version.
-        /// 
-        /// 
-        /// The prerelease version. Empty string if this is a release version.
-        /// 
-        public string Prerelease { get; }
-
-        /// 
-        /// Gets the build metadata.
-        /// 
-        /// 
-        /// The build metadata. Empty string if there is no build metadata.
-        /// 
-        public string Build { get; }
-
-        /// 
-        /// Returns the  equivalent of this version.
-        /// 
-        /// 
-        /// The  equivalent of this version.
-        /// 
-        public override string ToString()
-        {
-            // Assume all separators ("..-+"), at most 2 extra chars
-            var estimatedLength = 4 + Major.Digits() + Minor.Digits() + Patch.Digits()
-                                  + Prerelease.Length + Build.Length;
-            var version = new StringBuilder(estimatedLength);
-            version.Append(Major);
-            version.Append('.');
-            version.Append(Minor);
-            version.Append('.');
-            version.Append(Patch);
-            if (Prerelease.Length > 0)
-            {
-                version.Append('-');
-                version.Append(Prerelease);
-            }
-            if (Build.Length > 0)
-            {
-                version.Append('+');
-                version.Append(Build);
-            }
-            return version.ToString();
-        }
+    /// 
+    /// Gets the build metadata.
+    /// 
+    /// 
+    /// The build metadata. Empty string if there is no build metadata.
+    /// 
+    public string Build { get; }
 
-        /// 
-        /// Compares the current instance with another object of the same type and returns an integer that indicates
-        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
-        /// other object.
-        /// 
-        /// An object to compare with this instance.
-        /// 
-        /// A value that indicates the relative order of the objects being compared.
-        /// The return value has these meanings:
-        ///  Less than zero: This instance precedes  in the sort order.
-        ///  Zero: This instance occurs in the same position in the sort order as .
-        ///  Greater than zero: This instance follows  in the sort order.
-        /// 
-        /// The  is not a .
-        public int CompareTo(object obj)
+    /// 
+    /// Returns the  equivalent of this version.
+    /// 
+    /// 
+    /// The  equivalent of this version.
+    /// 
+    public override string ToString()
+    {
+        // Assume all separators ("..-+"), at most 2 extra chars
+        var estimatedLength = 4 + Major.Digits() + Minor.Digits() + Patch.Digits()
+                              + Prerelease.Length + Build.Length;
+        var version = new StringBuilder(estimatedLength);
+        version.Append(Major);
+        version.Append('.');
+        version.Append(Minor);
+        version.Append('.');
+        version.Append(Patch);
+        if (Prerelease.Length > 0)
         {
-            return CompareTo((SemVersion)obj);
+            version.Append('-');
+            version.Append(Prerelease);
         }
-
-        /// 
-        /// Compares the current instance with another object of the same type and returns an integer that indicates
-        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
-        /// other object.
-        /// 
-        /// An object to compare with this instance.
-        /// 
-        /// A value that indicates the relative order of the objects being compared.
-        /// The return value has these meanings:
-        ///  Less than zero: This instance precedes  in the sort order.
-        ///  Zero: This instance occurs in the same position in the sort order as .
-        ///  Greater than zero: This instance follows  in the sort order.
-        /// 
-        public int CompareTo(SemVersion other)
+        if (Build.Length > 0)
         {
-            var r = CompareByPrecedence(other);
-            if (r != 0) return r;
-
-            return CompareComponent(Prerelease, other.Prerelease, true);
+            version.Append('+');
+            version.Append(Build);
         }
+        return version.ToString();
+    }
 
-        /// 
-        /// Returns whether two semantic versions have the same precedence. Versions
-        /// that differ only by build metadata have the same precedence.
-        /// 
-        /// The semantic version to compare to.
-        ///  if the version precedences are equal.
-        public bool PrecedenceMatches(SemVersion other)
-        {
-            return CompareByPrecedence(other) == 0;
-        }
+    /// 
+    /// Compares the current instance with another object of the same type and returns an integer that indicates
+    /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
+    /// other object.
+    /// 
+    /// An object to compare with this instance.
+    /// 
+    /// A value that indicates the relative order of the objects being compared.
+    /// The return value has these meanings:
+    ///  Less than zero: This instance precedes  in the sort order.
+    ///  Zero: This instance occurs in the same position in the sort order as .
+    ///  Greater than zero: This instance follows  in the sort order.
+    /// 
+    /// The  is not a .
+    public int CompareTo(object obj)
+    {
+        return CompareTo((SemVersion)obj);
+    }
 
-        /// 
-        /// Compares two semantic versions by precedence as defined in the SemVer spec. Versions
-        /// that differ only by build metadata have the same precedence.
-        /// 
-        /// The semantic version.
-        /// 
-        /// A value that indicates the relative order of the objects being compared.
-        /// The return value has these meanings:
-        ///  Less than zero: This instance precedes  in the sort order.
-        ///  Zero: This instance occurs in the same position in the sort order as .
-        ///  Greater than zero: This instance follows  in the sort order.
-        /// 
-        public int CompareByPrecedence(SemVersion other)
-        {
-            if (other is null)
-                return 1;
+    /// 
+    /// Compares the current instance with another object of the same type and returns an integer that indicates
+    /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
+    /// other object.
+    /// 
+    /// An object to compare with this instance.
+    /// 
+    /// A value that indicates the relative order of the objects being compared.
+    /// The return value has these meanings:
+    ///  Less than zero: This instance precedes  in the sort order.
+    ///  Zero: This instance occurs in the same position in the sort order as .
+    ///  Greater than zero: This instance follows  in the sort order.
+    /// 
+    public int CompareTo(SemVersion other)
+    {
+        var r = CompareByPrecedence(other);
+        return r != 0 ? r : CompareComponent(Prerelease, other.Prerelease, true);
+    }
+
+    /// 
+    /// Returns whether two semantic versions have the same precedence. Versions
+    /// that differ only by build metadata have the same precedence.
+    /// 
+    /// The semantic version to compare to.
+    ///  if the version precedences are equal.
+    public bool PrecedenceMatches(SemVersion other)
+    {
+        return CompareByPrecedence(other) == 0;
+    }
 
-            var r = Major.CompareTo(other.Major);
-            if (r != 0) return r;
+    /// 
+    /// Compares two semantic versions by precedence as defined in the SemVer spec. Versions
+    /// that differ only by build metadata have the same precedence.
+    /// 
+    /// The semantic version.
+    /// 
+    /// A value that indicates the relative order of the objects being compared.
+    /// The return value has these meanings:
+    ///  Less than zero: This instance precedes  in the sort order.
+    ///  Zero: This instance occurs in the same position in the sort order as .
+    ///  Greater than zero: This instance follows  in the sort order.
+    /// 
+    public int CompareByPrecedence(SemVersion other)
+    {
+        if (other is null)
+            return 1;
 
-            r = Minor.CompareTo(other.Minor);
-            if (r != 0) return r;
+        var r = Major.CompareTo(other.Major);
+        if (r != 0)
+            return r;
 
-            r = Patch.CompareTo(other.Patch);
-            if (r != 0) return r;
+        r = Minor.CompareTo(other.Minor);
+        if (r != 0)
+            return r;
 
-            return CompareComponent(Prerelease, other.Prerelease, true);
-        }
+        r = Patch.CompareTo(other.Patch);
+        return r != 0 ? r : CompareComponent(Prerelease, other.Prerelease, true);
+    }
 
-        private static int CompareComponent(string a, string b, bool nonemptyIsLower = false)
-        {
-            var aEmpty = string.IsNullOrEmpty(a);
-            var bEmpty = string.IsNullOrEmpty(b);
-            if (aEmpty && bEmpty)
-                return 0;
+    private static int CompareComponent(string a, string b, bool nonemptyIsLower = false)
+    {
+        var aEmpty = string.IsNullOrEmpty(a);
+        var bEmpty = string.IsNullOrEmpty(b);
+        if (aEmpty && bEmpty)
+            return 0;
 
-            if (aEmpty)
-                return nonemptyIsLower ? 1 : -1;
-            if (bEmpty)
-                return nonemptyIsLower ? -1 : 1;
+        if (aEmpty)
+            return nonemptyIsLower ? 1 : -1;
+        if (bEmpty)
+            return nonemptyIsLower ? -1 : 1;
 
-            var aComps = a.Split('.');
-            var bComps = b.Split('.');
+        var aComps = a.Split('.');
+        var bComps = b.Split('.');
 
-            var minLen = Math.Min(aComps.Length, bComps.Length);
-            for (int i = 0; i < minLen; i++)
+        var minLen = Math.Min(aComps.Length, bComps.Length);
+        for (var i = 0; i < minLen; i++)
+        {
+            var ac = aComps[i];
+            var bc = bComps[i];
+            var aIsNum = int.TryParse(ac, out var aNum);
+            var bIsNum = int.TryParse(bc, out var bNum);
+            int r;
+            if (aIsNum && bIsNum)
             {
-                var ac = aComps[i];
-                var bc = bComps[i];
-                var aIsNum = int.TryParse(ac, out var aNum);
-                var bIsNum = int.TryParse(bc, out var bNum);
-                int r;
-                if (aIsNum && bIsNum)
-                {
-                    r = aNum.CompareTo(bNum);
-                    if (r != 0) return r;
-                }
-                else
-                {
-                    if (aIsNum)
-                        return -1;
-                    if (bIsNum)
-                        return 1;
-                    r = string.CompareOrdinal(ac, bc);
-                    if (r != 0)
-                        return r;
-                }
+                r = aNum.CompareTo(bNum);
+                if (r != 0)
+                    return r;
+            }
+            else
+            {
+                if (aIsNum)
+                    return -1;
+                if (bIsNum)
+                    return 1;
+                r = string.CompareOrdinal(ac, bc);
+                if (r != 0)
+                    return r;
             }
-
-            return aComps.Length.CompareTo(bComps.Length);
         }
 
-        /// 
-        /// Determines whether the specified  is equal to this instance.
-        /// 
-        /// The  to compare with this instance.
-        /// 
-        ///    if the specified  is equal to this instance, otherwise .
-        /// 
-        /// The  is not a .
-        public override bool Equals(object obj)
-        {
-            if (obj is null)
-                return false;
+        return aComps.Length.CompareTo(bComps.Length);
+    }
 
-            if (ReferenceEquals(this, obj))
-                return true;
+    /// 
+    /// Determines whether the specified  is equal to this instance.
+    /// 
+    /// The  to compare with this instance.
+    /// 
+    ///    if the specified  is equal to this instance, otherwise .
+    /// 
+    /// The  is not a .
+    public override bool Equals(object obj)
+    {
+        if (obj is null)
+            return false;
 
-            var other = (SemVersion)obj;
+        if (ReferenceEquals(this, obj))
+            return true;
 
-            return Major == other.Major
-                && Minor == other.Minor
-                && Patch == other.Patch
-                && string.Equals(Prerelease, other.Prerelease, StringComparison.Ordinal)
-                && string.Equals(Build, other.Build, StringComparison.Ordinal);
-        }
+        var other = (SemVersion)obj;
 
-        /// 
-        /// Returns a hash code for this instance.
-        /// 
-        /// 
-        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
-        /// 
-        public override int GetHashCode()
+        return Major == other.Major
+            && Minor == other.Minor
+            && Patch == other.Patch
+            && string.Equals(Prerelease, other.Prerelease, StringComparison.Ordinal)
+            && string.Equals(Build, other.Build, StringComparison.Ordinal);
+    }
+
+    /// 
+    /// Returns a hash code for this instance.
+    /// 
+    /// 
+    /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+    /// 
+    public override int GetHashCode()
+    {
+        unchecked
         {
-            unchecked
-            {
-                // TODO verify this. Some versions start result = 17. Some use 37 instead of 31
-                int result = Major.GetHashCode();
-                result = result * 31 + Minor.GetHashCode();
-                result = result * 31 + Patch.GetHashCode();
-                result = result * 31 + Prerelease.GetHashCode();
-                result = result * 31 + Build.GetHashCode();
-                return result;
-            }
+            // TODO verify this. Some versions start result = 17. Some use 37 instead of 31
+            var result = Major.GetHashCode();
+            result = (result * 31) + Minor.GetHashCode();
+            result = (result * 31) + Patch.GetHashCode();
+            result = (result * 31) + Prerelease.GetHashCode();
+            result = (result * 31) + Build.GetHashCode();
+            return result;
         }
+    }
 
 #if !NETSTANDARD
-        /// 
-        /// Populates a  with the data needed to serialize the target object.
-        /// 
-        /// The  to populate with data.
-        /// The destination (see ) for this serialization.
-        public void GetObjectData(SerializationInfo info, StreamingContext context)
-        {
-            if (info == null) throw new ArgumentNullException(nameof(info));
-            info.AddValue("SemVersion", ToString());
-        }
+    /// 
+    /// Populates a  with the data needed to serialize the target object.
+    /// 
+    /// The  to populate with data.
+    /// The destination (see ) for this serialization.
+    public void GetObjectData(SerializationInfo info, StreamingContext context)
+    {
+        if (info == null)
+            throw new ArgumentNullException(nameof(info));
+        info.AddValue("SemVersion", ToString());
+    }
 #endif
 
-        /// 
-        /// Implicit conversion from  to .
-        /// 
-        /// The semantic version.
-        /// The  object.
-        /// The  is .
-        /// The version number has an invalid format.
-        /// The Major, Minor, or Patch versions are larger than int.MaxValue.
-        public static implicit operator SemVersion(string version)
-        {
-            return Parse(version);
-        }
+    /// 
+    /// Implicit conversion from  to .
+    /// 
+    /// The semantic version.
+    /// The  object.
+    /// The  is .
+    /// The version number has an invalid format.
+    /// The Major, Minor, or Patch versions are larger than int.MaxValue.
+    public static implicit operator SemVersion(string version)
+    {
+        return Parse(version);
+    }
 
-        /// 
-        /// Compares two semantic versions for equality.
-        /// 
-        /// The left value.
-        /// The right value.
-        /// If left is equal to right , otherwise .
-        public static bool operator ==(SemVersion left, SemVersion right)
-        {
-            return Equals(left, right);
-        }
+    /// 
+    /// Compares two semantic versions for equality.
+    /// 
+    /// The left value.
+    /// The right value.
+    /// If left is equal to right , otherwise .
+    public static bool operator ==(SemVersion left, SemVersion right)
+    {
+        return Equals(left, right);
+    }
 
-        /// 
-        /// Compares two semantic versions for inequality.
-        /// 
-        /// The left value.
-        /// The right value.
-        /// If left is not equal to right , otherwise .
-        public static bool operator !=(SemVersion left, SemVersion right)
-        {
-            return !Equals(left, right);
-        }
+    /// 
+    /// Compares two semantic versions for inequality.
+    /// 
+    /// The left value.
+    /// The right value.
+    /// If left is not equal to right , otherwise .
+    public static bool operator !=(SemVersion left, SemVersion right)
+    {
+        return !Equals(left, right);
+    }
 
-        /// 
-        /// Compares two semantic versions.
-        /// 
-        /// The left value.
-        /// The right value.
-        /// If left is greater than right , otherwise .
-        public static bool operator >(SemVersion left, SemVersion right)
-        {
-            return Compare(left, right) > 0;
-        }
+    /// 
+    /// Compares two semantic versions.
+    /// 
+    /// The left value.
+    /// The right value.
+    /// If left is greater than right , otherwise .
+    public static bool operator >(SemVersion left, SemVersion right)
+    {
+        return Compare(left, right) > 0;
+    }
 
-        /// 
-        /// Compares two semantic versions.
-        /// 
-        /// The left value.
-        /// The right value.
-        /// If left is greater than or equal to right , otherwise .
-        public static bool operator >=(SemVersion left, SemVersion right)
-        {
-            return Equals(left, right) || Compare(left, right) > 0;
-        }
+    /// 
+    /// Compares two semantic versions.
+    /// 
+    /// The left value.
+    /// The right value.
+    /// If left is greater than or equal to right , otherwise .
+    public static bool operator >=(SemVersion left, SemVersion right)
+    {
+        return Equals(left, right) || Compare(left, right) > 0;
+    }
 
-        /// 
-        /// Compares two semantic versions.
-        /// 
-        /// The left value.
-        /// The right value.
-        /// If left is less than right , otherwise .
-        public static bool operator <(SemVersion left, SemVersion right)
-        {
-            return Compare(left, right) < 0;
-        }
+    /// 
+    /// Compares two semantic versions.
+    /// 
+    /// The left value.
+    /// The right value.
+    /// If left is less than right , otherwise .
+    public static bool operator <(SemVersion left, SemVersion right)
+    {
+        return Compare(left, right) < 0;
+    }
 
-        /// 
-        /// Compares two semantic versions.
-        /// 
-        /// The left value.
-        /// The right value.
-        /// If left is less than or equal to right , otherwise .
-        public static bool operator <=(SemVersion left, SemVersion right)
-        {
-            return Equals(left, right) || Compare(left, right) < 0;
-        }
+    /// 
+    /// Compares two semantic versions.
+    /// 
+    /// The left value.
+    /// The right value.
+    /// If left is less than or equal to right , otherwise .
+    public static bool operator <=(SemVersion left, SemVersion right)
+    {
+        return Equals(left, right) || Compare(left, right) < 0;
     }
 }
diff --git a/MelonLoader/SupportModule.cs b/MelonLoader/SupportModule.cs
index e7c26d01c..9338edae0 100644
--- a/MelonLoader/SupportModule.cs
+++ b/MelonLoader/SupportModule.cs
@@ -1,120 +1,119 @@
-using System;
+using MelonLoader.Utils;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Reflection;
-using MelonLoader.Utils;
 
-namespace MelonLoader
+namespace MelonLoader;
+
+internal static class SupportModule
 {
-    internal static class SupportModule
-    {
-        internal static ISupportModule_To Interface = null;
+    internal static ISupportModule_To Interface = null;
+
+    private static string BaseDirectory = null;
+    private static readonly List Modules =
+    [
+        new ModuleListing("Il2Cpp.dll", MelonUtils.IsGameIl2Cpp),
+        new ModuleListing("Mono.dll", () => !MelonUtils.IsGameIl2Cpp())
+    ];
 
-        private static string BaseDirectory = null;
-        private static List Modules = new List()
+    internal static bool Setup()
+    {
+        BaseDirectory = MelonEnvironment.SupportModuleDirectory;
+        if (!Directory.Exists(BaseDirectory))
         {
-            new ModuleListing("Il2Cpp.dll", MelonUtils.IsGameIl2Cpp),
-            new ModuleListing("Mono.dll", () => !MelonUtils.IsGameIl2Cpp())
-        };
+            MelonLogger.Error("Failed to Find SupportModules Directory!");
+            return false;
+        }
 
-        internal static bool Setup()
+        var enumerator = new LemonEnumerator(Modules);
+        while (enumerator.MoveNext())
         {
-            BaseDirectory = MelonEnvironment.SupportModuleDirectory;
-            if (!Directory.Exists(BaseDirectory))
-            {
-                MelonLogger.Error("Failed to Find SupportModules Directory!");
-                return false;
-            }
+            var ModulePath = Path.Combine(BaseDirectory, enumerator.Current.FileName);
+            if (!File.Exists(ModulePath))
+                continue;
 
-            LemonEnumerator enumerator = new LemonEnumerator(Modules);
-            while (enumerator.MoveNext())
+            try
             {
-                string ModulePath = Path.Combine(BaseDirectory, enumerator.Current.FileName);
-                if (!File.Exists(ModulePath))
-                    continue;
-
-                try
+                if (enumerator.Current.LoadSpecifier != null)
                 {
-                    if (enumerator.Current.LoadSpecifier != null)
+                    if (!enumerator.Current.LoadSpecifier())
                     {
-                        if (!enumerator.Current.LoadSpecifier())
-                        {
-                            //File.Delete(ModulePath);
-                            //string depsJson = Path.Combine(Path.GetDirectoryName(ModulePath), 
-                            //    Path.GetFileNameWithoutExtension(ModulePath) + ".deps.json");
-                            //if (File.Exists(depsJson))
-                            //    File.Delete(depsJson);
-
-                            continue;
-                        }
-                    }
+                        //File.Delete(ModulePath);
+                        //string depsJson = Path.Combine(Path.GetDirectoryName(ModulePath), 
+                        //    Path.GetFileNameWithoutExtension(ModulePath) + ".deps.json");
+                        //if (File.Exists(depsJson))
+                        //    File.Delete(depsJson);
 
-                    if (Interface != null)
-                        continue;
-
-                    if (!LoadInterface(ModulePath))
                         continue;
+                    }
                 }
-                catch (Exception ex)
-                {
-                    MelonDebug.Error($"Support Module [{enumerator.Current.FileName}] threw an Exception: {ex}");
+
+                if (Interface != null)
                     continue;
-                }
-            }
 
-            if (Interface == null)
+                if (!LoadInterface(ModulePath))
+                    continue;
+            }
+            catch (Exception ex)
             {
-                MelonLogger.Error("No Support Module Loaded!");
-                return false;
+                MelonDebug.Error($"Support Module [{enumerator.Current.FileName}] threw an Exception: {ex}");
+                continue;
             }
-            return true;
         }
 
-        private static bool LoadInterface(string ModulePath)
+        if (Interface == null)
         {
-            Assembly assembly = Assembly.LoadFrom(ModulePath);
-            if (assembly == null)
-                return false;
-
-            Type type = assembly.GetType("MelonLoader.Support.Main");
-            if (type == null)
-            {
-                MelonLogger.Error("Failed to Get Type MelonLoader.Support.Main!");
-                return false;
-            }
+            MelonLogger.Error("No Support Module Loaded!");
+            return false;
+        }
+        return true;
+    }
 
-            MethodInfo method = type.GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Static);
-            if (method == null)
-            {
-                MelonLogger.Error("Failed to Get Method Initialize!");
-                return false;
-            }
+    private static bool LoadInterface(string ModulePath)
+    {
+        var assembly = Assembly.LoadFrom(ModulePath);
+        if (assembly == null)
+            return false;
 
-            Interface = (ISupportModule_To)method.Invoke(null, new object[] { new SupportModule_From() });
-            if (Interface == null)
-            {
-                MelonLogger.Error("Failed to Initialize Interface!");
-                return false;
-            }
+        var type = assembly.GetType("MelonLoader.Support.Main");
+        if (type == null)
+        {
+            MelonLogger.Error("Failed to Get Type MelonLoader.Support.Main!");
+            return false;
+        }
 
-            MelonLogger.Msg($"Support Module Loaded: {ModulePath}");
+        var method = type.GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Static);
+        if (method == null)
+        {
+            MelonLogger.Error("Failed to Get Method Initialize!");
+            return false;
+        }
 
-            return true;
+        Interface = (ISupportModule_To)method.Invoke(null, new object[] { new SupportModule_From() });
+        if (Interface == null)
+        {
+            MelonLogger.Error("Failed to Initialize Interface!");
+            return false;
         }
 
-        // Module Listing
-        private class ModuleListing
+        MelonLogger.Msg($"Support Module Loaded: {ModulePath}");
+
+        return true;
+    }
+
+    // Module Listing
+    private class ModuleListing
+    {
+        internal string FileName = null;
+        internal delegate bool dLoadSpecifier();
+        internal dLoadSpecifier LoadSpecifier = null;
+        internal ModuleListing(string filename)
+            => FileName = filename;
+        internal ModuleListing(string filename, dLoadSpecifier loadSpecifier)
         {
-            internal string FileName = null;
-            internal delegate bool dLoadSpecifier();
-            internal dLoadSpecifier LoadSpecifier = null;
-            internal ModuleListing(string filename)
-                => FileName = filename;
-            internal ModuleListing(string filename, dLoadSpecifier loadSpecifier)
-            {
-                FileName = filename;
-                LoadSpecifier = loadSpecifier;
-            }
+            FileName = filename;
+            LoadSpecifier = loadSpecifier;
         }
     }
 }
\ No newline at end of file
diff --git a/MelonLoader/SupportModule_From.cs b/MelonLoader/SupportModule_From.cs
index 371752935..55b764f9b 100644
--- a/MelonLoader/SupportModule_From.cs
+++ b/MelonLoader/SupportModule_From.cs
@@ -1,44 +1,42 @@
-namespace MelonLoader
+namespace MelonLoader;
+
+internal class SupportModule_From : ISupportModule_From
 {
-    internal class SupportModule_From : ISupportModule_From
-    {
-        public void OnApplicationLateStart()
-            => MelonEvents.OnApplicationLateStart.Invoke();
+    public void OnApplicationLateStart()
+        => MelonEvents.OnApplicationLateStart.Invoke();
 
-        public void OnSceneWasLoaded(int buildIndex, string sceneName)
-            => MelonEvents.OnSceneWasLoaded.Invoke(buildIndex, sceneName);
+    public void OnSceneWasLoaded(int buildIndex, string sceneName)
+        => MelonEvents.OnSceneWasLoaded.Invoke(buildIndex, sceneName);
 
-        public void OnSceneWasInitialized(int buildIndex, string sceneName)
-            => MelonEvents.OnSceneWasInitialized.Invoke(buildIndex, sceneName);
+    public void OnSceneWasInitialized(int buildIndex, string sceneName)
+        => MelonEvents.OnSceneWasInitialized.Invoke(buildIndex, sceneName);
 
-        public void OnSceneWasUnloaded(int buildIndex, string sceneName)
-            => MelonEvents.OnSceneWasUnloaded.Invoke(buildIndex, sceneName);
+    public void OnSceneWasUnloaded(int buildIndex, string sceneName)
+        => MelonEvents.OnSceneWasUnloaded.Invoke(buildIndex, sceneName);
 
-        public void Update()
-            => MelonEvents.OnUpdate.Invoke();
+    public void Update()
+        => MelonEvents.OnUpdate.Invoke();
 
-        public void FixedUpdate()
-            => MelonEvents.OnFixedUpdate.Invoke();
+    public void FixedUpdate()
+        => MelonEvents.OnFixedUpdate.Invoke();
 
-        public void LateUpdate()
-            => MelonEvents.OnLateUpdate.Invoke();
+    public void LateUpdate()
+        => MelonEvents.OnLateUpdate.Invoke();
 
-        public void OnGUI()
-            => MelonEvents.OnGUI.Invoke();
+    public void OnGUI()
+        => MelonEvents.OnGUI.Invoke();
 
-        public void Quit()
-            => MelonEvents.OnApplicationQuit.Invoke();
+    public void Quit()
+        => MelonEvents.OnApplicationQuit.Invoke();
 
-        public void DefiniteQuit()
-        {
-            MelonEvents.OnApplicationDefiniteQuit.Invoke();
-            Core.Quit();
-        }
+    public void DefiniteQuit()
+    {
+        MelonEvents.OnApplicationDefiniteQuit.Invoke();
+        Core.Quit();
+    }
 
-        public void SetInteropSupportInterface(InteropSupport.Interface interop)
-        {
-            if (InteropSupport.SMInterface == null)
-                InteropSupport.SMInterface = interop;
-        }
+    public void SetInteropSupportInterface(InteropSupport.Interface interop)
+    {
+        InteropSupport.SMInterface ??= interop;
     }
 }
\ No newline at end of file
diff --git a/MelonLoader/TinyJSON/Decoder.cs b/MelonLoader/TinyJSON/Decoder.cs
index 5f433c2eb..f0e41b264 100644
--- a/MelonLoader/TinyJSON/Decoder.cs
+++ b/MelonLoader/TinyJSON/Decoder.cs
@@ -1,392 +1,353 @@
+using System;
 using System.IO;
 using System.Text;
-using System;
 
-namespace MelonLoader.TinyJSON
+namespace MelonLoader.TinyJSON;
+
+public sealed class Decoder : IDisposable
 {
-	public sealed class Decoder : IDisposable
-	{
-		const string whiteSpace = " \t\n\r";
-		const string wordBreak = " \t\n\r{}[],:\"";
-
-		enum Token
-		{
-			None,
-			OpenBrace,
-			CloseBrace,
-			OpenBracket,
-			CloseBracket,
-			Colon,
-			Comma,
-			String,
-			Number,
-			True,
-			False,
-			Null
-		}
-
-		StringReader json;
-
-
-		Decoder( string jsonString )
-		{
-			json = new StringReader( jsonString );
-		}
-
-
-		public static Variant Decode( string jsonString )
-		{
-			using (var instance = new Decoder( jsonString ))
-			{
-				return instance.DecodeValue();
-			}
-		}
-
-
-		public void Dispose()
-		{
-			json.Dispose();
-			json = null;
-		}
-
-
-		ProxyObject DecodeObject()
-		{
-			var proxy = new ProxyObject();
-
-			// Ditch opening brace.
-			json.Read();
-
-			// {
-			while (true)
-			{
-				// ReSharper disable once SwitchStatementMissingSomeCases
-				switch (NextToken)
-				{
-					case Token.None:
-						return null;
-
-					case Token.Comma:
-						continue;
-
-					case Token.CloseBrace:
-						return proxy;
-
-					default:
-						// Key
-						string key = DecodeString();
-						if (key == null)
-						{
-							return null;
-						}
-
-						// :
-						if (NextToken != Token.Colon)
-						{
-							return null;
-						}
-
-						json.Read();
-
-						// Value
-						proxy.Add( key, DecodeValue() );
-						break;
-				}
-			}
-		}
-
-
-		ProxyArray DecodeArray()
-		{
-			var proxy = new ProxyArray();
-
-			// Ditch opening bracket.
-			json.Read();
-
-			// [
-			var parsing = true;
-			while (parsing)
-			{
-				var nextToken = NextToken;
-
-				// ReSharper disable once SwitchStatementMissingSomeCases
-				switch (nextToken)
-				{
-					case Token.None:
-						return null;
-
-					case Token.Comma:
-						continue;
-
-					case Token.CloseBracket:
-						parsing = false;
-						break;
-
-					default:
-						proxy.Add( DecodeByToken( nextToken ) );
-						break;
-				}
-			}
-
-			return proxy;
-		}
-
-
-		Variant DecodeValue()
-		{
-			var nextToken = NextToken;
-			return DecodeByToken( nextToken );
-		}
-
-
-		Variant DecodeByToken( Token token )
-		{
-			// ReSharper disable once SwitchStatementMissingSomeCases
-			switch (token)
-			{
-				case Token.String:
-					return DecodeString();
-
-				case Token.Number:
-					return DecodeNumber();
-
-				case Token.OpenBrace:
-					return DecodeObject();
-
-				case Token.OpenBracket:
-					return DecodeArray();
-
-				case Token.True:
-					return new ProxyBoolean( true );
-
-				case Token.False:
-					return new ProxyBoolean( false );
-
-				case Token.Null:
-					return null;
-
-				default:
-					return null;
-			}
-		}
-
-
-		Variant DecodeString()
-		{
-			var stringBuilder = new StringBuilder();
-
-			// ditch opening quote
-			json.Read();
-
-			var parsing = true;
-			while (parsing)
-			{
-				if (json.Peek() == -1)
-				{
-					// ReSharper disable once RedundantAssignment
-					parsing = false;
-					break;
-				}
-
-				var c = NextChar;
-				switch (c)
-				{
-					case '"':
-						parsing = false;
-						break;
-
-					case '\\':
-						if (json.Peek() == -1)
-						{
-							parsing = false;
-							break;
-						}
-
-						c = NextChar;
-
-						// ReSharper disable once SwitchStatementMissingSomeCases
-						switch (c)
-						{
-							case '"':
-							case '\\':
-							case '/':
-								stringBuilder.Append( c );
-								break;
-
-							case 'b':
-								stringBuilder.Append( '\b' );
-								break;
-
-							case 'f':
-								stringBuilder.Append( '\f' );
-								break;
-
-							case 'n':
-								stringBuilder.Append( '\n' );
-								break;
-
-							case 'r':
-								stringBuilder.Append( '\r' );
-								break;
-
-							case 't':
-								stringBuilder.Append( '\t' );
-								break;
-
-							case 'u':
-								var hex = new StringBuilder();
-
-								for (var i = 0; i < 4; i++)
-								{
-									hex.Append( NextChar );
-								}
-
-								stringBuilder.Append( (char) Convert.ToInt32( hex.ToString(), 16 ) );
-								break;
-
-							//default:
-							//	throw new DecodeException( @"Illegal character following escape character: " + c );
-						}
-
-						break;
-
-					default:
-						stringBuilder.Append( c );
-						break;
-				}
-			}
-
-			return new ProxyString( stringBuilder.ToString() );
-		}
-
-
-		Variant DecodeNumber()
-		{
-			return new ProxyNumber( NextWord );
-		}
-
-
-		void ConsumeWhiteSpace()
-		{
-			while (whiteSpace.IndexOf( PeekChar ) != -1)
-			{
-				json.Read();
-
-				if (json.Peek() == -1)
-				{
-					break;
-				}
-			}
-		}
-
-
-		char PeekChar
-		{
-			get
-			{
-				var peek = json.Peek();
-				return peek == -1 ? '\0' : Convert.ToChar( peek );
-			}
-		}
-
-
-		char NextChar
-		{
-			get
-			{
-				return Convert.ToChar( json.Read() );
-			}
-		}
-
-
-		string NextWord
-		{
-			get
-			{
-				var word = new StringBuilder();
-
-				while (wordBreak.IndexOf( PeekChar ) == -1)
-				{
-					word.Append( NextChar );
-
-					if (json.Peek() == -1)
-					{
-						break;
-					}
-				}
-
-				return word.ToString();
-			}
-		}
-
-
-		Token NextToken
-		{
-			get
-			{
-				ConsumeWhiteSpace();
-
-				if (json.Peek() == -1)
-				{
-					return Token.None;
-				}
-
-				// ReSharper disable once SwitchStatementMissingSomeCases
-				switch (PeekChar)
-				{
-					case '{':
-						return Token.OpenBrace;
-
-					case '}':
-						json.Read();
-						return Token.CloseBrace;
-
-					case '[':
-						return Token.OpenBracket;
-
-					case ']':
-						json.Read();
-						return Token.CloseBracket;
-
-					case ',':
-						json.Read();
-						return Token.Comma;
-
-					case '"':
-						return Token.String;
-
-					case ':':
-						return Token.Colon;
-
-					case '0':
-					case '1':
-					case '2':
-					case '3':
-					case '4':
-					case '5':
-					case '6':
-					case '7':
-					case '8':
-					case '9':
-					case '-':
-						return Token.Number;
-				}
-
-				// ReSharper disable once SwitchStatementMissingSomeCases
-				switch (NextWord)
-				{
-					case "false":
-						return Token.False;
-
-					case "true":
-						return Token.True;
-
-					case "null":
-						return Token.Null;
-				}
-
-				return Token.None;
-			}
-		}
-	}
+    private const string whiteSpace = " \t\n\r";
+    private const string wordBreak = " \t\n\r{}[],:\"";
+
+    private enum Token
+    {
+        None,
+        OpenBrace,
+        CloseBrace,
+        OpenBracket,
+        CloseBracket,
+        Colon,
+        Comma,
+        String,
+        Number,
+        True,
+        False,
+        Null
+    }
+
+    private StringReader json;
+
+    private Decoder(string jsonString)
+    {
+        json = new StringReader(jsonString);
+    }
+
+    public static Variant Decode(string jsonString)
+    {
+        using var instance = new Decoder(jsonString);
+        return instance.DecodeValue();
+    }
+
+    public void Dispose()
+    {
+        json.Dispose();
+        json = null;
+    }
+
+    private ProxyObject DecodeObject()
+    {
+        var proxy = new ProxyObject();
+
+        // Ditch opening brace.
+        json.Read();
+
+        // {
+        while (true)
+        {
+            // ReSharper disable once SwitchStatementMissingSomeCases
+            switch (NextToken)
+            {
+                case Token.None:
+                    return null;
+
+                case Token.Comma:
+                    continue;
+
+                case Token.CloseBrace:
+                    return proxy;
+
+                default:
+                    // Key
+                    string key = DecodeString();
+                    if (key == null)
+                    {
+                        return null;
+                    }
+
+                    // :
+                    if (NextToken != Token.Colon)
+                    {
+                        return null;
+                    }
+
+                    json.Read();
+
+                    // Value
+                    proxy.Add(key, DecodeValue());
+                    break;
+            }
+        }
+    }
+
+    private ProxyArray DecodeArray()
+    {
+        var proxy = new ProxyArray();
+
+        // Ditch opening bracket.
+        json.Read();
+
+        // [
+        var parsing = true;
+        while (parsing)
+        {
+            var nextToken = NextToken;
+
+            // ReSharper disable once SwitchStatementMissingSomeCases
+            switch (nextToken)
+            {
+                case Token.None:
+                    return null;
+
+                case Token.Comma:
+                    continue;
+
+                case Token.CloseBracket:
+                    parsing = false;
+                    break;
+
+                default:
+                    proxy.Add(DecodeByToken(nextToken));
+                    break;
+            }
+        }
+
+        return proxy;
+    }
+
+    private Variant DecodeValue()
+    {
+        var nextToken = NextToken;
+        return DecodeByToken(nextToken);
+    }
+
+    private Variant DecodeByToken(Token token)
+    {
+        // ReSharper disable once SwitchStatementMissingSomeCases
+        return token switch
+        {
+            Token.String => DecodeString(),
+            Token.Number => DecodeNumber(),
+            Token.OpenBrace => DecodeObject(),
+            Token.OpenBracket => DecodeArray(),
+            Token.True => new ProxyBoolean(true),
+            Token.False => new ProxyBoolean(false),
+            Token.Null => null,
+            _ => null,
+        };
+    }
+
+    private Variant DecodeString()
+    {
+        var stringBuilder = new StringBuilder();
+
+        // ditch opening quote
+        json.Read();
+
+        var parsing = true;
+        while (parsing)
+        {
+            if (json.Peek() == -1)
+            {
+                // ReSharper disable once RedundantAssignment
+                break;
+            }
+
+            var c = NextChar;
+            switch (c)
+            {
+                case '"':
+                    parsing = false;
+                    break;
+
+                case '\\':
+                    if (json.Peek() == -1)
+                    {
+                        parsing = false;
+                        break;
+                    }
+
+                    c = NextChar;
+
+                    // ReSharper disable once SwitchStatementMissingSomeCases
+                    switch (c)
+                    {
+                        case '"':
+                        case '\\':
+                        case '/':
+                            stringBuilder.Append(c);
+                            break;
+
+                        case 'b':
+                            stringBuilder.Append('\b');
+                            break;
+
+                        case 'f':
+                            stringBuilder.Append('\f');
+                            break;
+
+                        case 'n':
+                            stringBuilder.Append('\n');
+                            break;
+
+                        case 'r':
+                            stringBuilder.Append('\r');
+                            break;
+
+                        case 't':
+                            stringBuilder.Append('\t');
+                            break;
+
+                        case 'u':
+                            var hex = new StringBuilder();
+
+                            for (var i = 0; i < 4; i++)
+                            {
+                                hex.Append(NextChar);
+                            }
+
+                            stringBuilder.Append((char)Convert.ToInt32(hex.ToString(), 16));
+                            break;
+
+                            //default:
+                            //	throw new DecodeException( @"Illegal character following escape character: " + c );
+                    }
+
+                    break;
+
+                default:
+                    stringBuilder.Append(c);
+                    break;
+            }
+        }
+
+        return new ProxyString(stringBuilder.ToString());
+    }
+
+    private Variant DecodeNumber()
+    {
+        return new ProxyNumber(NextWord);
+    }
+
+    private void ConsumeWhiteSpace()
+    {
+        while (whiteSpace.IndexOf(PeekChar) != -1)
+        {
+            json.Read();
+
+            if (json.Peek() == -1)
+            {
+                break;
+            }
+        }
+    }
+
+    private char PeekChar
+    {
+        get
+        {
+            var peek = json.Peek();
+            return peek == -1 ? '\0' : Convert.ToChar(peek);
+        }
+    }
+
+    private char NextChar
+    {
+        get
+        {
+            return Convert.ToChar(json.Read());
+        }
+    }
+
+    private string NextWord
+    {
+        get
+        {
+            var word = new StringBuilder();
+
+            while (wordBreak.IndexOf(PeekChar) == -1)
+            {
+                word.Append(NextChar);
+
+                if (json.Peek() == -1)
+                {
+                    break;
+                }
+            }
+
+            return word.ToString();
+        }
+    }
+
+    private Token NextToken
+    {
+        get
+        {
+            ConsumeWhiteSpace();
+
+            if (json.Peek() == -1)
+            {
+                return Token.None;
+            }
+
+            // ReSharper disable once SwitchStatementMissingSomeCases
+            switch (PeekChar)
+            {
+                case '{':
+                    return Token.OpenBrace;
+
+                case '}':
+                    json.Read();
+                    return Token.CloseBrace;
+
+                case '[':
+                    return Token.OpenBracket;
+
+                case ']':
+                    json.Read();
+                    return Token.CloseBracket;
+
+                case ',':
+                    json.Read();
+                    return Token.Comma;
+
+                case '"':
+                    return Token.String;
+
+                case ':':
+                    return Token.Colon;
+
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                case '-':
+                    return Token.Number;
+            }
+
+            // ReSharper disable once SwitchStatementMissingSomeCases
+            return NextWord switch
+            {
+                "false" => Token.False,
+                "true" => Token.True,
+                "null" => Token.Null,
+                _ => Token.None,
+            };
+        }
+    }
 }
diff --git a/MelonLoader/TinyJSON/EncodeOptions.cs b/MelonLoader/TinyJSON/EncodeOptions.cs
index b2fbaad24..1061b3614 100644
--- a/MelonLoader/TinyJSON/EncodeOptions.cs
+++ b/MelonLoader/TinyJSON/EncodeOptions.cs
@@ -1,17 +1,16 @@
 using System;
 
-namespace MelonLoader.TinyJSON
+namespace MelonLoader.TinyJSON;
+
+[Flags]
+public enum EncodeOptions
 {
-	[Flags]
-	public enum EncodeOptions
-	{
-		None = 0,
-		PrettyPrint = 1,
-		NoTypeHints = 2,
-		IncludePublicProperties = 4,
-		EnforceHierarchyOrder = 8,
+    None = 0,
+    PrettyPrint = 1,
+    NoTypeHints = 2,
+    IncludePublicProperties = 4,
+    EnforceHierarchyOrder = 8,
 
-		[Obsolete( "Use EncodeOptions.EnforceHierarchyOrder instead." )]
-		EnforceHeirarchyOrder = EnforceHierarchyOrder
-	}
+    [Obsolete("Use EncodeOptions.EnforceHierarchyOrder instead.")]
+    EnforceHeirarchyOrder = EnforceHierarchyOrder
 }
diff --git a/MelonLoader/TinyJSON/Encoder.cs b/MelonLoader/TinyJSON/Encoder.cs
index ed13201fe..9193e84c9 100644
--- a/MelonLoader/TinyJSON/Encoder.cs
+++ b/MelonLoader/TinyJSON/Encoder.cs
@@ -5,607 +5,580 @@
 using System.Reflection;
 using System.Text;
 
-namespace MelonLoader.TinyJSON
-{
-	public sealed class Encoder
-	{
-		static readonly Type includeAttrType = typeof(Include);
-		static readonly Type excludeAttrType = typeof(Exclude);
-		static readonly Type typeHintAttrType = typeof(TypeHint);
-
-		readonly StringBuilder builder;
-		readonly EncodeOptions options;
-		int indent;
-
-
-		Encoder( EncodeOptions options )
-		{
-			this.options = options;
-			builder = new StringBuilder();
-			indent = 0;
-		}
-
-
-		// ReSharper disable once UnusedMember.Global
-		public static string Encode( object obj )
-		{
-			return Encode( obj, EncodeOptions.None );
-		}
-
-
-		public static string Encode( object obj, EncodeOptions options )
-		{
-			var instance = new Encoder( options );
-			instance.EncodeValue( obj, false );
-			return instance.builder.ToString();
-		}
-
-
-		bool PrettyPrintEnabled
-		{
-			get
-			{
-				return (options & EncodeOptions.PrettyPrint) == EncodeOptions.PrettyPrint;
-			}
-		}
-
-
-		bool TypeHintsEnabled
-		{
-			get
-			{
-				return (options & EncodeOptions.NoTypeHints) != EncodeOptions.NoTypeHints;
-			}
-		}
-
-
-		bool IncludePublicPropertiesEnabled
-		{
-			get
-			{
-				return (options & EncodeOptions.IncludePublicProperties) == EncodeOptions.IncludePublicProperties;
-			}
-		}
-
-
-		bool EnforceHierarchyOrderEnabled
-		{
-			get
-			{
-				return (options & EncodeOptions.EnforceHierarchyOrder) == EncodeOptions.EnforceHierarchyOrder;
-			}
-		}
-
-
-		void EncodeValue( object value, bool forceTypeHint )
-		{
-			if (value == null)
-			{
-				builder.Append( "null" );
-				return;
-			}
-
-			if (value is string)
-			{
-				EncodeString( (string) value );
-				return;
-			}
-
-			if (value is ProxyString)
-			{
-				EncodeString( ((ProxyString) value).ToString( CultureInfo.InvariantCulture ) );
-				return;
-			}
-
-			if (value is char)
-			{
-				EncodeString( value.ToString() );
-				return;
-			}
-
-			if (value is bool)
-			{
-				builder.Append( (bool) value ? "true" : "false" );
-				return;
-			}
-
-			if (value is Enum)
-			{
-				EncodeString( value.ToString() );
-				return;
-			}
-
-			if (value is Array)
-			{
-				EncodeArray( (Array) value, forceTypeHint );
-				return;
-			}
-
-			if (value is IList)
-			{
-				EncodeList( (IList) value, forceTypeHint );
-				return;
-			}
-
-			if (value is IDictionary)
-			{
-				EncodeDictionary( (IDictionary) value, forceTypeHint );
-				return;
-			}
-
-			if (value is Guid)
-			{
-				EncodeString( value.ToString() );
-				return;
-			}
-
-			if (value is ProxyArray)
-			{
-				EncodeProxyArray( (ProxyArray) value );
-				return;
-			}
-
-			if (value is ProxyObject)
-			{
-				EncodeProxyObject( (ProxyObject) value );
-				return;
-			}
-
-			if (value is float ||
-			    value is double ||
-			    value is int ||
-			    value is uint ||
-			    value is long ||
-			    value is sbyte ||
-			    value is byte ||
-			    value is short ||
-			    value is ushort ||
-			    value is ulong ||
-			    value is decimal ||
-			    value is ProxyBoolean ||
-			    value is ProxyNumber)
-			{
-				builder.Append( Convert.ToString( value, CultureInfo.InvariantCulture ) );
-				return;
-			}
-
-			EncodeObject( value, forceTypeHint );
-		}
-
-
-		IEnumerable GetFieldsForType( Type type )
-		{
-			if (EnforceHierarchyOrderEnabled)
-			{
-				var types = new Stack();
-				while (type != null)
-				{
-					types.Push( type );
-					type = type.BaseType;
-				}
-
-				var fields = new List();
-				while (types.Count > 0)
-				{
-					fields.AddRange( types.Pop().GetFields( BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) );
-				}
-
-				return fields;
-			}
-
-			return type.GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
-		}
-
-
-		IEnumerable GetPropertiesForType( Type type )
-		{
-			if (EnforceHierarchyOrderEnabled)
-			{
-				var types = new Stack();
-				while (type != null)
-				{
-					types.Push( type );
-					type = type.BaseType;
-				}
-
-				var properties = new List();
-				while (types.Count > 0)
-				{
-					properties.AddRange( types.Pop().GetProperties( BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) );
-				}
-
-				return properties;
-			}
-
-			return type.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
-		}
-
-
-		void EncodeObject( object value, bool forceTypeHint )
-		{
-			var type = value.GetType();
-
-			AppendOpenBrace();
-
-			forceTypeHint = forceTypeHint || TypeHintsEnabled;
-
-			var includePublicProperties = IncludePublicPropertiesEnabled;
-
-			var firstItem = !forceTypeHint;
-			if (forceTypeHint)
-			{
-				if (PrettyPrintEnabled)
-				{
-					AppendIndent();
-				}
-
-				EncodeString( ProxyObject.TypeHintKey );
-				AppendColon();
-				EncodeString( type.FullName );
-
-				// ReSharper disable once RedundantAssignment
-				firstItem = false;
-			}
-
-			foreach (var field in GetFieldsForType( type ))
-			{
-				var shouldTypeHint = false;
-				var shouldEncode = field.IsPublic;
-				foreach (var attribute in field.GetCustomAttributes( true ))
-				{
-					if (excludeAttrType.IsInstanceOfType( attribute ))
-					{
-						shouldEncode = false;
-					}
-
-					if (includeAttrType.IsInstanceOfType( attribute ))
-					{
-						shouldEncode = true;
-					}
-
-					if (typeHintAttrType.IsInstanceOfType( attribute ))
-					{
-						shouldTypeHint = true;
-					}
-				}
-
-				if (shouldEncode)
-				{
-					AppendComma( firstItem );
-					EncodeString( field.Name );
-					AppendColon();
-					EncodeValue( field.GetValue( value ), shouldTypeHint );
-					firstItem = false;
-				}
-			}
-
-			foreach (var property in GetPropertiesForType( type ))
-			{
-				if (property.CanRead)
-				{
-					var shouldTypeHint = false;
-					var shouldEncode = includePublicProperties;
-
-					foreach (var attribute in property.GetCustomAttributes( true ))
-					{
-						if (excludeAttrType.IsInstanceOfType( attribute ))
-						{
-							shouldEncode = false;
-						}
-
-						if (includeAttrType.IsInstanceOfType( attribute ))
-						{
-							shouldEncode = true;
-						}
-
-						if (typeHintAttrType.IsInstanceOfType( attribute ))
-						{
-							shouldTypeHint = true;
-						}
-					}
-
-					if (shouldEncode)
-					{
-						AppendComma( firstItem );
-						EncodeString( property.Name );
-						AppendColon();
-						EncodeValue( property.GetValue( value, null ), shouldTypeHint );
-						firstItem = false;
-					}
-				}
-			}
-
-			AppendCloseBrace();
-		}
-
-
-		void EncodeProxyArray( ProxyArray value )
-		{
-			if (value.Count == 0)
-			{
-				builder.Append( "[]" );
-			}
-			else
-			{
-				AppendOpenBracket();
-
-				var firstItem = true;
-				foreach (var obj in value)
-				{
-					AppendComma( firstItem );
-					EncodeValue( obj, false );
-					firstItem = false;
-				}
-
-				AppendCloseBracket();
-			}
-		}
-
-
-		void EncodeProxyObject( ProxyObject value )
-		{
-			if (value.Count == 0)
-			{
-				builder.Append( "{}" );
-			}
-			else
-			{
-				AppendOpenBrace();
-
-				var firstItem = true;
-				foreach (var e in value.Keys)
-				{
-					AppendComma( firstItem );
-					EncodeString( e );
-					AppendColon();
-					EncodeValue( value[e], false );
-					firstItem = false;
-				}
-
-				AppendCloseBrace();
-			}
-		}
-
-
-		void EncodeDictionary( IDictionary value, bool forceTypeHint )
-		{
-			if (value.Count == 0)
-			{
-				builder.Append( "{}" );
-			}
-			else
-			{
-				AppendOpenBrace();
-
-				var firstItem = true;
-				foreach (var e in value.Keys)
-				{
-					AppendComma( firstItem );
-					EncodeString( e.ToString() );
-					AppendColon();
-					EncodeValue( value[e], forceTypeHint );
-					firstItem = false;
-				}
-
-				AppendCloseBrace();
-			}
-		}
-
-
-		// ReSharper disable once SuggestBaseTypeForParameter
-		void EncodeList( IList value, bool forceTypeHint )
-		{
-			if (value.Count == 0)
-			{
-				builder.Append( "[]" );
-			}
-			else
-			{
-				AppendOpenBracket();
-
-				var firstItem = true;
-				foreach (var obj in value)
-				{
-					AppendComma( firstItem );
-					EncodeValue( obj, forceTypeHint );
-					firstItem = false;
-				}
-
-				AppendCloseBracket();
-			}
-		}
-
-
-		void EncodeArray( Array value, bool forceTypeHint )
-		{
-			if (value.Rank == 1)
-			{
-				EncodeList( value, forceTypeHint );
-			}
-			else
-			{
-				var indices = new int[value.Rank];
-				EncodeArrayRank( value, 0, indices, forceTypeHint );
-			}
-		}
-
-
-		void EncodeArrayRank( Array value, int rank, int[] indices, bool forceTypeHint )
-		{
-			AppendOpenBracket();
-
-			var min = value.GetLowerBound( rank );
-			var max = value.GetUpperBound( rank );
-
-			if (rank == value.Rank - 1)
-			{
-				for (var i = min; i <= max; i++)
-				{
-					indices[rank] = i;
-					AppendComma( i == min );
-					EncodeValue( value.GetValue( indices ), forceTypeHint );
-				}
-			}
-			else
-			{
-				for (var i = min; i <= max; i++)
-				{
-					indices[rank] = i;
-					AppendComma( i == min );
-					EncodeArrayRank( value, rank + 1, indices, forceTypeHint );
-				}
-			}
-
-			AppendCloseBracket();
-		}
-
-
-		void EncodeString( string value )
-		{
-			builder.Append( '\"' );
-
-			var charArray = value.ToCharArray();
-			foreach (var c in charArray)
-			{
-				switch (c)
-				{
-					case '"':
-						builder.Append( "\\\"" );
-						break;
-
-					case '\\':
-						builder.Append( "\\\\" );
-						break;
-
-					case '\b':
-						builder.Append( "\\b" );
-						break;
-
-					case '\f':
-						builder.Append( "\\f" );
-						break;
-
-					case '\n':
-						builder.Append( "\\n" );
-						break;
-
-					case '\r':
-						builder.Append( "\\r" );
-						break;
-
-					case '\t':
-						builder.Append( "\\t" );
-						break;
-
-					default:
-						var codepoint = Convert.ToInt32( c );
-						if ((codepoint >= 32) && (codepoint <= 126))
-						{
-							builder.Append( c );
-						}
-						else
-						{
-							builder.Append( "\\u" + Convert.ToString( codepoint, 16 ).PadLeft( 4, '0' ) );
-						}
-
-						break;
-				}
-			}
-
-			builder.Append( '\"' );
-		}
-
-
-		#region Helpers
-
-		void AppendIndent()
-		{
-			for (var i = 0; i < indent; i++)
-			{
-				builder.Append( '\t' );
-			}
-		}
-
-
-		void AppendOpenBrace()
-		{
-			builder.Append( '{' );
-
-			if (PrettyPrintEnabled)
-			{
-				builder.Append( '\n' );
-				indent++;
-			}
-		}
-
-
-		void AppendCloseBrace()
-		{
-			if (PrettyPrintEnabled)
-			{
-				builder.Append( '\n' );
-				indent--;
-				AppendIndent();
-			}
-
-			builder.Append( '}' );
-		}
-
-
-		void AppendOpenBracket()
-		{
-			builder.Append( '[' );
-
-			if (PrettyPrintEnabled)
-			{
-				builder.Append( '\n' );
-				indent++;
-			}
-		}
-
-
-		void AppendCloseBracket()
-		{
-			if (PrettyPrintEnabled)
-			{
-				builder.Append( '\n' );
-				indent--;
-				AppendIndent();
-			}
-
-			builder.Append( ']' );
-		}
-
-
-		void AppendComma( bool firstItem )
-		{
-			if (!firstItem)
-			{
-				builder.Append( ',' );
-
-				if (PrettyPrintEnabled)
-				{
-					builder.Append( '\n' );
-				}
-			}
-
-			if (PrettyPrintEnabled)
-			{
-				AppendIndent();
-			}
-		}
-
-
-		void AppendColon()
-		{
-			builder.Append( ':' );
-
-			if (PrettyPrintEnabled)
-			{
-				builder.Append( ' ' );
-			}
-		}
+namespace MelonLoader.TinyJSON;
 
-		#endregion
-	}
+public sealed class Encoder
+{
+    private static readonly Type includeAttrType = typeof(Include);
+    private static readonly Type excludeAttrType = typeof(Exclude);
+    private static readonly Type typeHintAttrType = typeof(TypeHint);
+    private readonly StringBuilder builder;
+    private readonly EncodeOptions options;
+    private int indent;
+
+    private Encoder(EncodeOptions options)
+    {
+        this.options = options;
+        builder = new StringBuilder();
+        indent = 0;
+    }
+
+    // ReSharper disable once UnusedMember.Global
+    public static string Encode(object obj)
+    {
+        return Encode(obj, EncodeOptions.None);
+    }
+
+    public static string Encode(object obj, EncodeOptions options)
+    {
+        var instance = new Encoder(options);
+        instance.EncodeValue(obj, false);
+        return instance.builder.ToString();
+    }
+
+    private bool PrettyPrintEnabled
+    {
+        get
+        {
+            return (options & EncodeOptions.PrettyPrint) == EncodeOptions.PrettyPrint;
+        }
+    }
+
+    private bool TypeHintsEnabled
+    {
+        get
+        {
+            return (options & EncodeOptions.NoTypeHints) != EncodeOptions.NoTypeHints;
+        }
+    }
+
+    private bool IncludePublicPropertiesEnabled
+    {
+        get
+        {
+            return (options & EncodeOptions.IncludePublicProperties) == EncodeOptions.IncludePublicProperties;
+        }
+    }
+
+    private bool EnforceHierarchyOrderEnabled
+    {
+        get
+        {
+            return (options & EncodeOptions.EnforceHierarchyOrder) == EncodeOptions.EnforceHierarchyOrder;
+        }
+    }
+
+    private void EncodeValue(object value, bool forceTypeHint)
+    {
+        if (value == null)
+        {
+            builder.Append("null");
+            return;
+        }
+
+        if (value is string)
+        {
+            EncodeString((string)value);
+            return;
+        }
+
+        if (value is ProxyString)
+        {
+            EncodeString(((ProxyString)value).ToString(CultureInfo.InvariantCulture));
+            return;
+        }
+
+        if (value is char)
+        {
+            EncodeString(value.ToString());
+            return;
+        }
+
+        if (value is bool)
+        {
+            builder.Append((bool)value ? "true" : "false");
+            return;
+        }
+
+        if (value is Enum)
+        {
+            EncodeString(value.ToString());
+            return;
+        }
+
+        if (value is Array)
+        {
+            EncodeArray((Array)value, forceTypeHint);
+            return;
+        }
+
+        if (value is IList)
+        {
+            EncodeList((IList)value, forceTypeHint);
+            return;
+        }
+
+        if (value is IDictionary)
+        {
+            EncodeDictionary((IDictionary)value, forceTypeHint);
+            return;
+        }
+
+        if (value is Guid)
+        {
+            EncodeString(value.ToString());
+            return;
+        }
+
+        if (value is ProxyArray)
+        {
+            EncodeProxyArray((ProxyArray)value);
+            return;
+        }
+
+        if (value is ProxyObject)
+        {
+            EncodeProxyObject((ProxyObject)value);
+            return;
+        }
+
+        if (value is float or
+        double or
+        int or
+        uint or
+        long or
+        sbyte or
+        byte or
+        short or
+        ushort or
+        ulong or
+        decimal or
+        ProxyBoolean or
+        ProxyNumber)
+        {
+            builder.Append(Convert.ToString(value, CultureInfo.InvariantCulture));
+            return;
+        }
+
+        EncodeObject(value, forceTypeHint);
+    }
+
+    private IEnumerable GetFieldsForType(Type type)
+    {
+        if (EnforceHierarchyOrderEnabled)
+        {
+            var types = new Stack();
+            while (type != null)
+            {
+                types.Push(type);
+                type = type.BaseType;
+            }
+
+            var fields = new List();
+            while (types.Count > 0)
+            {
+                fields.AddRange(types.Pop().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
+            }
+
+            return fields;
+        }
+
+        return type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+    }
+
+    private IEnumerable GetPropertiesForType(Type type)
+    {
+        if (EnforceHierarchyOrderEnabled)
+        {
+            var types = new Stack();
+            while (type != null)
+            {
+                types.Push(type);
+                type = type.BaseType;
+            }
+
+            var properties = new List();
+            while (types.Count > 0)
+            {
+                properties.AddRange(types.Pop().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
+            }
+
+            return properties;
+        }
+
+        return type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+    }
+
+    private void EncodeObject(object value, bool forceTypeHint)
+    {
+        var type = value.GetType();
+
+        AppendOpenBrace();
+
+        forceTypeHint = forceTypeHint || TypeHintsEnabled;
+
+        var includePublicProperties = IncludePublicPropertiesEnabled;
+
+        var firstItem = !forceTypeHint;
+        if (forceTypeHint)
+        {
+            if (PrettyPrintEnabled)
+            {
+                AppendIndent();
+            }
+
+            EncodeString(ProxyObject.TypeHintKey);
+            AppendColon();
+            EncodeString(type.FullName);
+
+            // ReSharper disable once RedundantAssignment
+            firstItem = false;
+        }
+
+        foreach (var field in GetFieldsForType(type))
+        {
+            var shouldTypeHint = false;
+            var shouldEncode = field.IsPublic;
+            foreach (var attribute in field.GetCustomAttributes(true))
+            {
+                if (excludeAttrType.IsInstanceOfType(attribute))
+                {
+                    shouldEncode = false;
+                }
+
+                if (includeAttrType.IsInstanceOfType(attribute))
+                {
+                    shouldEncode = true;
+                }
+
+                if (typeHintAttrType.IsInstanceOfType(attribute))
+                {
+                    shouldTypeHint = true;
+                }
+            }
+
+            if (shouldEncode)
+            {
+                AppendComma(firstItem);
+                EncodeString(field.Name);
+                AppendColon();
+                EncodeValue(field.GetValue(value), shouldTypeHint);
+                firstItem = false;
+            }
+        }
+
+        foreach (var property in GetPropertiesForType(type))
+        {
+            if (property.CanRead)
+            {
+                var shouldTypeHint = false;
+                var shouldEncode = includePublicProperties;
+
+                foreach (var attribute in property.GetCustomAttributes(true))
+                {
+                    if (excludeAttrType.IsInstanceOfType(attribute))
+                    {
+                        shouldEncode = false;
+                    }
+
+                    if (includeAttrType.IsInstanceOfType(attribute))
+                    {
+                        shouldEncode = true;
+                    }
+
+                    if (typeHintAttrType.IsInstanceOfType(attribute))
+                    {
+                        shouldTypeHint = true;
+                    }
+                }
+
+                if (shouldEncode)
+                {
+                    AppendComma(firstItem);
+                    EncodeString(property.Name);
+                    AppendColon();
+                    EncodeValue(property.GetValue(value, null), shouldTypeHint);
+                    firstItem = false;
+                }
+            }
+        }
+
+        AppendCloseBrace();
+    }
+
+    private void EncodeProxyArray(ProxyArray value)
+    {
+        if (value.Count == 0)
+        {
+            builder.Append("[]");
+        }
+        else
+        {
+            AppendOpenBracket();
+
+            var firstItem = true;
+            foreach (var obj in value)
+            {
+                AppendComma(firstItem);
+                EncodeValue(obj, false);
+                firstItem = false;
+            }
+
+            AppendCloseBracket();
+        }
+    }
+
+    private void EncodeProxyObject(ProxyObject value)
+    {
+        if (value.Count == 0)
+        {
+            builder.Append("{}");
+        }
+        else
+        {
+            AppendOpenBrace();
+
+            var firstItem = true;
+            foreach (var e in value.Keys)
+            {
+                AppendComma(firstItem);
+                EncodeString(e);
+                AppendColon();
+                EncodeValue(value[e], false);
+                firstItem = false;
+            }
+
+            AppendCloseBrace();
+        }
+    }
+
+    private void EncodeDictionary(IDictionary value, bool forceTypeHint)
+    {
+        if (value.Count == 0)
+        {
+            builder.Append("{}");
+        }
+        else
+        {
+            AppendOpenBrace();
+
+            var firstItem = true;
+            foreach (var e in value.Keys)
+            {
+                AppendComma(firstItem);
+                EncodeString(e.ToString());
+                AppendColon();
+                EncodeValue(value[e], forceTypeHint);
+                firstItem = false;
+            }
+
+            AppendCloseBrace();
+        }
+    }
+
+    // ReSharper disable once SuggestBaseTypeForParameter
+    private void EncodeList(IList value, bool forceTypeHint)
+    {
+        if (value.Count == 0)
+        {
+            builder.Append("[]");
+        }
+        else
+        {
+            AppendOpenBracket();
+
+            var firstItem = true;
+            foreach (var obj in value)
+            {
+                AppendComma(firstItem);
+                EncodeValue(obj, forceTypeHint);
+                firstItem = false;
+            }
+
+            AppendCloseBracket();
+        }
+    }
+
+    private void EncodeArray(Array value, bool forceTypeHint)
+    {
+        if (value.Rank == 1)
+        {
+            EncodeList(value, forceTypeHint);
+        }
+        else
+        {
+            var indices = new int[value.Rank];
+            EncodeArrayRank(value, 0, indices, forceTypeHint);
+        }
+    }
+
+    private void EncodeArrayRank(Array value, int rank, int[] indices, bool forceTypeHint)
+    {
+        AppendOpenBracket();
+
+        var min = value.GetLowerBound(rank);
+        var max = value.GetUpperBound(rank);
+
+        if (rank == value.Rank - 1)
+        {
+            for (var i = min; i <= max; i++)
+            {
+                indices[rank] = i;
+                AppendComma(i == min);
+                EncodeValue(value.GetValue(indices), forceTypeHint);
+            }
+        }
+        else
+        {
+            for (var i = min; i <= max; i++)
+            {
+                indices[rank] = i;
+                AppendComma(i == min);
+                EncodeArrayRank(value, rank + 1, indices, forceTypeHint);
+            }
+        }
+
+        AppendCloseBracket();
+    }
+
+    private void EncodeString(string value)
+    {
+        builder.Append('\"');
+
+        var charArray = value.ToCharArray();
+        foreach (var c in charArray)
+        {
+            switch (c)
+            {
+                case '"':
+                    builder.Append("\\\"");
+                    break;
+
+                case '\\':
+                    builder.Append("\\\\");
+                    break;
+
+                case '\b':
+                    builder.Append("\\b");
+                    break;
+
+                case '\f':
+                    builder.Append("\\f");
+                    break;
+
+                case '\n':
+                    builder.Append("\\n");
+                    break;
+
+                case '\r':
+                    builder.Append("\\r");
+                    break;
+
+                case '\t':
+                    builder.Append("\\t");
+                    break;
+
+                default:
+                    var codepoint = Convert.ToInt32(c);
+                    if (codepoint is >= 32 and <= 126)
+                    {
+                        builder.Append(c);
+                    }
+                    else
+                    {
+                        builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0'));
+                    }
+
+                    break;
+            }
+        }
+
+        builder.Append('\"');
+    }
+
+    #region Helpers
+
+    private void AppendIndent()
+    {
+        for (var i = 0; i < indent; i++)
+        {
+            builder.Append('\t');
+        }
+    }
+
+    private void AppendOpenBrace()
+    {
+        builder.Append('{');
+
+        if (PrettyPrintEnabled)
+        {
+            builder.Append('\n');
+            indent++;
+        }
+    }
+
+    private void AppendCloseBrace()
+    {
+        if (PrettyPrintEnabled)
+        {
+            builder.Append('\n');
+            indent--;
+            AppendIndent();
+        }
+
+        builder.Append('}');
+    }
+
+    private void AppendOpenBracket()
+    {
+        builder.Append('[');
+
+        if (PrettyPrintEnabled)
+        {
+            builder.Append('\n');
+            indent++;
+        }
+    }
+
+    private void AppendCloseBracket()
+    {
+        if (PrettyPrintEnabled)
+        {
+            builder.Append('\n');
+            indent--;
+            AppendIndent();
+        }
+
+        builder.Append(']');
+    }
+
+    private void AppendComma(bool firstItem)
+    {
+        if (!firstItem)
+        {
+            builder.Append(',');
+
+            if (PrettyPrintEnabled)
+            {
+                builder.Append('\n');
+            }
+        }
+
+        if (PrettyPrintEnabled)
+        {
+            AppendIndent();
+        }
+    }
+
+    private void AppendColon()
+    {
+        builder.Append(':');
+
+        if (PrettyPrintEnabled)
+        {
+            builder.Append(' ');
+        }
+    }
+
+    #endregion
 }
diff --git a/MelonLoader/TinyJSON/Extensions.cs b/MelonLoader/TinyJSON/Extensions.cs
index de57daeb8..76c7f663d 100644
--- a/MelonLoader/TinyJSON/Extensions.cs
+++ b/MelonLoader/TinyJSON/Extensions.cs
@@ -1,31 +1,30 @@
 using System;
 using System.Collections.Generic;
 
-namespace MelonLoader.TinyJSON
+namespace MelonLoader.TinyJSON;
+
+public static class Extensions
 {
-	public static class Extensions
-	{
-		public static bool AnyOfType( this IEnumerable source, Type expectedType )
-		{
-			if (source == null)
-			{
-				throw new ArgumentNullException( "source" );
-			}
+    public static bool AnyOfType(this IEnumerable source, Type expectedType)
+    {
+        if (source == null)
+        {
+            throw new ArgumentNullException("source");
+        }
 
-			if (expectedType == null)
-			{
-				throw new ArgumentNullException( "expectedType" );
-			}
+        if (expectedType == null)
+        {
+            throw new ArgumentNullException("expectedType");
+        }
 
-			foreach (var item in source)
-			{
-				if (expectedType.IsInstanceOfType( item ))
-				{
-					return true;
-				}
-			}
+        foreach (var item in source)
+        {
+            if (expectedType.IsInstanceOfType(item))
+            {
+                return true;
+            }
+        }
 
-			return false;
-		}
-	}
+        return false;
+    }
 }
diff --git a/MelonLoader/TinyJSON/JSON.cs b/MelonLoader/TinyJSON/JSON.cs
index 05f27fc19..16ca4bc75 100644
--- a/MelonLoader/TinyJSON/JSON.cs
+++ b/MelonLoader/TinyJSON/JSON.cs
@@ -4,658 +4,627 @@
 using System.Globalization;
 using System.Reflection;
 
-namespace MelonLoader.TinyJSON
+namespace MelonLoader.TinyJSON;
+
+/// 
+/// Mark members that should be included.
+/// Public fields are included by default.
+/// 
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+public sealed class Include : Attribute { }
+
+/// 
+/// Mark members that should be excluded.
+/// Private fields and all properties are excluded by default.
+/// 
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+public class Exclude : Attribute { }
+
+/// 
+/// Mark methods to be called after an object is decoded.
+/// 
+[AttributeUsage(AttributeTargets.Method)]
+public class AfterDecode : Attribute { }
+
+/// 
+/// Mark methods to be called before an object is encoded.
+/// 
+[AttributeUsage(AttributeTargets.Method)]
+public class BeforeEncode : Attribute { }
+
+/// 
+/// Mark members to force type hinting even when EncodeOptions.NoTypeHints is set.
+/// 
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+public class TypeHint : Attribute { }
+
+/// 
+/// Provide field and property aliases when an object is decoded.
+/// If a field or property is not found while decoding, this list will be searched for a matching alias.
+/// 
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
+public class DecodeAlias : Attribute
 {
-	/// 
-	/// Mark members that should be included.
-	/// Public fields are included by default.
-	/// 
-	[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )]
-	public sealed class Include : Attribute {}
+    public string[] Names { get; private set; }
 
+    public DecodeAlias(params string[] names)
+    {
+        Names = names;
+    }
 
-	/// 
-	/// Mark members that should be excluded.
-	/// Private fields and all properties are excluded by default.
-	/// 
-	[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )]
-	public class Exclude : Attribute {}
-
-
-	/// 
-	/// Mark methods to be called after an object is decoded.
-	/// 
-	[AttributeUsage( AttributeTargets.Method )]
-	public class AfterDecode : Attribute {}
-
-
-	/// 
-	/// Mark methods to be called before an object is encoded.
-	/// 
-	[AttributeUsage( AttributeTargets.Method )]
-	public class BeforeEncode : Attribute {}
-
-
-	/// 
-	/// Mark members to force type hinting even when EncodeOptions.NoTypeHints is set.
-	/// 
-	[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )]
-	public class TypeHint : Attribute {}
-
-
-	/// 
-	/// Provide field and property aliases when an object is decoded.
-	/// If a field or property is not found while decoding, this list will be searched for a matching alias.
-	/// 
-	[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true )]
-	public class DecodeAlias : Attribute
-	{
-		public string[] Names { get; private set; }
-
-
-		public DecodeAlias( params string[] names )
-		{
-			Names = names;
-		}
-
-
-		public bool Contains( string name )
-		{
-			return Array.IndexOf( Names, name ) > -1;
-		}
-	}
+    public bool Contains(string name)
+    {
+        return Array.IndexOf(Names, name) > -1;
+    }
+}
 
+[Obsolete("Use the Exclude attribute instead.")]
+// ReSharper disable once UnusedMember.Global
+public sealed class Skip : Exclude { }
 
-	[Obsolete( "Use the Exclude attribute instead." )]
-	// ReSharper disable once UnusedMember.Global
-	public sealed class Skip : Exclude {}
+[Obsolete("Use the AfterDecode attribute instead.")]
+// ReSharper disable once UnusedMember.Global
+public sealed class Load : AfterDecode { }
 
+public sealed class DecodeException : Exception
+{
+    public DecodeException(string message)
+        : base(message) { }
 
-	[Obsolete( "Use the AfterDecode attribute instead." )]
-	// ReSharper disable once UnusedMember.Global
-	public sealed class Load : AfterDecode {}
-
-
-	public sealed class DecodeException : Exception
-	{
-		public DecodeException( string message )
-			: base( message ) {}
+    public DecodeException(string message, Exception innerException)
+        : base(message, innerException) { }
+}
 
-
-		public DecodeException( string message, Exception innerException )
-			: base( message, innerException ) {}
-	}
-
-
-	// ReSharper disable once InconsistentNaming
-	public static class JSON
-	{
-		static readonly Type includeAttrType = typeof(Include);
-		static readonly Type excludeAttrType = typeof(Exclude);
-		static readonly Type decodeAliasAttrType = typeof(DecodeAlias);
-
-
-		public static Variant Load( string json )
-		{
-			if (string.IsNullOrEmpty(json))
-			{
-				throw new ArgumentNullException( "json" );
-			}
-
-			return Decoder.Decode( json );
-		}
-
-
-		public static string Dump( object data )
-		{
-			return Dump( data, EncodeOptions.None );
-		}
-
-
-		public static string Dump( object data, EncodeOptions options )
-		{
-			// Invoke methods tagged with [BeforeEncode] attribute.
-			if (data != null)
-			{
-				var type = data.GetType();
-				if (!(type.IsEnum || type.IsPrimitive || type.IsArray))
-				{
-					foreach (var method in type.GetMethods( instanceBindingFlags ))
-					{
-						if (method.GetCustomAttributes( false ).AnyOfType( typeof(BeforeEncode) ))
-						{
-							if (method.GetParameters().Length == 0)
-							{
-								method.Invoke( data, null );
-							}
-						}
-					}
-				}
-			}
-
-			return Encoder.Encode( data, options );
-		}
-
-
-		public static void MakeInto( Variant data, out T item )
-		{
-			item = DecodeType( data );
-		}
-
-
-		public static void Populate( Variant data, T item ) where T : class
-		{
-			if (item == null)
-			{
-				throw new ArgumentNullException( nameof(item) );
-			}
-			DecodeFields( data, ref item );
-		}
-
-
-		static readonly Dictionary typeCache = new Dictionary();
-
-		static Type FindType( string fullName )
-		{
-			if (fullName == null)
-			{
-				return null;
-			}
-
-			Type type;
-			if (typeCache.TryGetValue( fullName, out type ))
-			{
-				return type;
-			}
-
-			foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
-			{
-				type = assembly.GetType( fullName );
-				if (type != null)
-				{
-					typeCache.Add( fullName, type );
-					return type;
-				}
-			}
-
-			return null;
-		}
-
-		static T DecodeType( Variant data )
-		{
-			if (data == null)
-			{
-				return default(T);
-			}
-
-			var type = typeof(T);
-
-            Type nulledType = Nullable.GetUnderlyingType(type);
-            if (nulledType != null)
-			{
-				var makeFunc = decodeTypeMethod.MakeGenericMethod( nulledType );
-				var v = makeFunc.Invoke( null, new object[] { data } );
-				return (T) v;
-			}
-
-			if (type.IsEnum)
-			{
-				return (T) Enum.Parse( type, data.ToString( CultureInfo.InvariantCulture ) );
-			}
-
-			if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
-			{
-				return (T) Convert.ChangeType( data, type );
-			}
-
-			if (type == typeof(Guid))
-			{
-				return (T) (object) new Guid( data.ToString( CultureInfo.InvariantCulture ) );
-			}
-
-			if (type.IsArray)
-			{
-				if (type.GetArrayRank() == 1)
-				{
-					var makeFunc = decodeArrayMethod.MakeGenericMethod( type.GetElementType() );
-					return (T) makeFunc.Invoke( null, new object[] { data } );
-				}
-
-				var arrayData = data as ProxyArray;
-				if (arrayData == null)
-				{
-					throw new DecodeException( "Variant is expected to be a ProxyArray here, but it is not." );
-				}
-
-				var arrayRank = type.GetArrayRank();
-				var rankLengths = new int[arrayRank];
-				if (arrayData.CanBeMultiRankArray( rankLengths ))
-				{
-					var elementType = type.GetElementType();
-					if (elementType == null)
-					{
-						throw new DecodeException( "Array element type is expected to be not null, but it is." );
-					}
-
-					var array = Array.CreateInstance( elementType, rankLengths );
-					var makeFunc = decodeMultiRankArrayMethod.MakeGenericMethod( elementType );
-					try
-					{
-						makeFunc.Invoke( null, new object[] { arrayData, array, 1, rankLengths } );
-					}
-					catch (Exception e)
-					{
-						throw new DecodeException( "Error decoding multidimensional array. Did you try to decode into an array of incompatible rank or element type?", e );
-					}
-
-					return (T) Convert.ChangeType( array, typeof(T) );
-				}
-
-				throw new DecodeException( "Error decoding multidimensional array; JSON data doesn't seem fit this structure." );
-			}
-
-			if (typeof(IList).IsAssignableFrom( type ))
-			{
-				var makeFunc = decodeListMethod.MakeGenericMethod( type.GetGenericArguments() );
-				return (T) makeFunc.Invoke( null, new object[] { data } );
-			}
-
-			if (typeof(IDictionary).IsAssignableFrom( type ))
-			{
-				var makeFunc = decodeDictionaryMethod.MakeGenericMethod( type.GetGenericArguments() );
-				return (T) makeFunc.Invoke( null, new object[] { data } );
-			}
-
-			// At this point we should be dealing with a class or struct.
-			T instance;
-			var proxyObject = data as ProxyObject;
-			if (proxyObject == null)
-			{
-				throw new InvalidCastException( "ProxyObject expected when decoding into '" + type.FullName + "'." );
-			}
-
-			// If there's a type hint, use it to create the instance.
-			var typeHint = proxyObject.TypeHint;
-			if (typeHint != null && typeHint != type.FullName)
-			{
-				var makeType = FindType( typeHint );
-				if (makeType == null)
-				{
-					throw new TypeLoadException( "Could not load type '" + typeHint + "'." );
-				}
-
-				if (type.IsAssignableFrom( makeType ))
-				{
-					instance = (T) Activator.CreateInstance( makeType );
-					type = makeType;
-				}
-				else
-				{
-					throw new InvalidCastException( "Cannot assign type '" + typeHint + "' to type '" + type.FullName + "'." );
-				}
-			}
-			else
-			{
-				// We don't have a type hint, so just instantiate the type we have.
-				instance = Activator.CreateInstance();
-			}
-
-			foreach (var pair in proxyObject)
-			{
-				var field = type.GetField( pair.Key, instanceBindingFlags );
-
-				// If the field doesn't exist, search through any [DecodeAlias] attributes.
-				if (field == null)
-				{
-					var fields = type.GetFields( instanceBindingFlags );
-					foreach (var fieldInfo in fields)
-					{
-						foreach (var attribute in fieldInfo.GetCustomAttributes( true ))
-						{
-							if (decodeAliasAttrType.IsInstanceOfType( attribute ))
-							{
-								if (((DecodeAlias) attribute).Contains( pair.Key ))
-								{
-									field = fieldInfo;
-									break;
-								}
-							}
-						}
-					}
-				}
-
-				if (field != null)
-				{
-					var shouldDecode = field.IsPublic;
-					foreach (var attribute in field.GetCustomAttributes( true ))
-					{
-						if (excludeAttrType.IsInstanceOfType( attribute ))
-						{
-							shouldDecode = false;
-						}
-
-						if (includeAttrType.IsInstanceOfType( attribute ))
-						{
-							shouldDecode = true;
-						}
-					}
-
-					if (shouldDecode)
-					{
-						var makeFunc = decodeTypeMethod.MakeGenericMethod( field.FieldType );
-						if (type.IsValueType)
-						{
-							// Type is a struct.
-							var instanceRef = (object) instance;
-							field.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ) );
-							instance = (T) instanceRef;
-						}
-						else
-						{
-							// Type is a class.
-							field.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ) );
-						}
-					}
-				}
-
-				var property = type.GetProperty( pair.Key, instanceBindingFlags );
-
-				// If the property doesn't exist, search through any [DecodeAlias] attributes.
-				if (property == null)
-				{
-					var properties = type.GetProperties( instanceBindingFlags );
-					foreach (var propertyInfo in properties)
-					{
-						foreach (var attribute in propertyInfo.GetCustomAttributes( false ))
-						{
-							if (decodeAliasAttrType.IsInstanceOfType( attribute ))
-							{
-								if (((DecodeAlias) attribute).Contains( pair.Key ))
-								{
-									property = propertyInfo;
-									break;
-								}
-							}
-						}
-					}
-				}
-
-				if (property != null)
-				{
-					if (property.CanWrite && property.GetCustomAttributes( false ).AnyOfType( includeAttrType ))
-					{
-						var makeFunc = decodeTypeMethod.MakeGenericMethod( new Type[] { property.PropertyType } );
-						if (type.IsValueType)
-						{
-							// Type is a struct.
-							var instanceRef = (object) instance;
-							property.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ), null );
-							instance = (T) instanceRef;
-						}
-						else
-						{
-							// Type is a class.
-							property.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ), null );
-						}
-					}
-				}
-			}
-
-			// Invoke methods tagged with [AfterDecode] attribute.
-			foreach (var method in type.GetMethods( instanceBindingFlags ))
-			{
-				if (method.GetCustomAttributes( false ).AnyOfType( typeof(AfterDecode) ))
-				{
-					method.Invoke( instance, method.GetParameters().Length == 0 ? null : new object[] { data } );
-				}
-			}
-
-			return instance;
-		}
-
-		static void DecodeFields( Variant data, ref T instance )
-		{
-			var type = typeof(T);
-			var proxyObject = data as ProxyObject;
-			if (proxyObject == null)
-			{
-				throw new InvalidCastException( "ProxyObject expected when decoding into '" + type.FullName + "'." );
-			}
-
-			foreach (var pair in proxyObject)
-			{
-				var field = type.GetField( pair.Key, instanceBindingFlags );
-
-				// If the field doesn't exist, search through any [DecodeAlias] attributes.
-				if (field == null)
-				{
-					var fields = type.GetFields( instanceBindingFlags );
-					foreach (var fieldInfo in fields)
-					{
-						foreach (var attribute in fieldInfo.GetCustomAttributes( true ))
-						{
-							if (decodeAliasAttrType.IsInstanceOfType( attribute ))
-							{
-								if (((DecodeAlias) attribute).Contains( pair.Key ))
-								{
-									field = fieldInfo;
-									break;
-								}
-							}
-						}
-					}
-				}
-
-				if (field != null)
-				{
-					var shouldDecode = field.IsPublic;
-					foreach (var attribute in field.GetCustomAttributes( true ))
-					{
-						if (excludeAttrType.IsInstanceOfType( attribute ))
-						{
-							shouldDecode = false;
-						}
-
-						if (includeAttrType.IsInstanceOfType( attribute ))
-						{
-							shouldDecode = true;
-						}
-					}
-
-					if (shouldDecode)
-					{
-						var makeFunc = decodeTypeMethod.MakeGenericMethod( field.FieldType );
-						if (type.IsValueType)
-						{
-							// Type is a struct.
-							var instanceRef = (object) instance;
-							field.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ) );
-							instance = (T) instanceRef;
-						}
-						else
-						{
-							// Type is a class.
-							field.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ) );
-						}
-					}
-				}
-
-				var property = type.GetProperty( pair.Key, instanceBindingFlags );
-
-				// If the property doesn't exist, search through any [DecodeAlias] attributes.
-				if (property == null)
-				{
-					var properties = type.GetProperties( instanceBindingFlags );
-					foreach (var propertyInfo in properties)
-					{
-						foreach (var attribute in propertyInfo.GetCustomAttributes( false ))
-						{
-							if (decodeAliasAttrType.IsInstanceOfType( attribute ))
-							{
-								if (((DecodeAlias) attribute).Contains( pair.Key ))
-								{
-									property = propertyInfo;
-									break;
-								}
-							}
-						}
-					}
-				}
-
-				if (property != null)
-				{
-					if (property.CanWrite && property.GetCustomAttributes( false ).AnyOfType( includeAttrType ))
-					{
-						var makeFunc = decodeTypeMethod.MakeGenericMethod( new Type[] { property.PropertyType } );
-						if (type.IsValueType)
-						{
-							// Type is a struct.
-							var instanceRef = (object) instance;
-							property.SetValue( instanceRef, makeFunc.Invoke( null, new object[] { pair.Value } ), null );
-							instance = (T) instanceRef;
-						}
-						else
-						{
-							// Type is a class.
-							property.SetValue( instance, makeFunc.Invoke( null, new object[] { pair.Value } ), null );
-						}
-					}
-				}
-			}
-
-			// Invoke methods tagged with [AfterDecode] attribute.
-			foreach (var method in type.GetMethods( instanceBindingFlags ))
-			{
-				if (method.GetCustomAttributes( false ).AnyOfType( typeof(AfterDecode) ))
-				{
-					method.Invoke( instance, method.GetParameters().Length == 0 ? null : new object[] { data } );
-				}
-			}
-		}
-
-		// ReSharper disable once UnusedMethodReturnValue.Local
-		static List DecodeList( Variant data )
-		{
-			var list = new List();
-
-			var proxyArray = data as ProxyArray;
-			if (proxyArray == null)
-			{
-				throw new DecodeException( "Variant is expected to be a ProxyArray here, but it is not." );
-			}
-
-			foreach (var item in proxyArray)
-			{
-				list.Add( DecodeType( item ) );
-			}
-
-			return list;
-		}
-
-		// ReSharper disable once UnusedMethodReturnValue.Local
-		static Dictionary DecodeDictionary( Variant data )
-		{
-			var dict = new Dictionary();
-			var type = typeof(TKey);
-
-			var proxyObject = data as ProxyObject;
-			if (proxyObject == null)
-			{
-				throw new DecodeException( "Variant is expected to be a ProxyObject here, but it is not." );
-			}
-
-			foreach (var pair in proxyObject)
-			{
-				var k = (TKey) (type.IsEnum ? Enum.Parse( type, pair.Key ) : Convert.ChangeType( pair.Key, type ));
-				var v = DecodeType( pair.Value );
-				dict.Add( k, v );
-			}
-
-			return dict;
-		}
-
-		// ReSharper disable once UnusedMethodReturnValue.Local
-		static T[] DecodeArray( Variant data )
-		{
-			var arrayData = data as ProxyArray;
-			if (arrayData == null)
-			{
-				throw new DecodeException( "Variant is expected to be a ProxyArray here, but it is not." );
-			}
-
-			var arraySize = arrayData.Count;
-			var array = new T[arraySize];
-
-			var i = 0;
-			foreach (var item in arrayData)
-			{
-				array[i++] = DecodeType( item );
-			}
-
-			return array;
-		}
-
-		// ReSharper disable once UnusedMember.Local
-		static void DecodeMultiRankArray( ProxyArray arrayData, Array array, int arrayRank, int[] indices )
-		{
-			var count = arrayData.Count;
-			for (var i = 0; i < count; i++)
-			{
-				indices[arrayRank - 1] = i;
-				if (arrayRank < array.Rank)
-				{
-					DecodeMultiRankArray( arrayData[i] as ProxyArray, array, arrayRank + 1, indices );
-				}
-				else
-				{
-					array.SetValue( DecodeType( arrayData[i] ), indices );
-				}
-			}
-		}
-
-
-		const BindingFlags instanceBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
-		const BindingFlags staticBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
-		static readonly MethodInfo decodeTypeMethod = typeof(JSON).GetMethod( "DecodeType", staticBindingFlags );
-		static readonly MethodInfo decodeListMethod = typeof(JSON).GetMethod( "DecodeList", staticBindingFlags );
-		static readonly MethodInfo decodeDictionaryMethod = typeof(JSON).GetMethod( "DecodeDictionary", staticBindingFlags );
-		static readonly MethodInfo decodeArrayMethod = typeof(JSON).GetMethod( "DecodeArray", staticBindingFlags );
-		static readonly MethodInfo decodeMultiRankArrayMethod = typeof(JSON).GetMethod( "DecodeMultiRankArray", staticBindingFlags );
-
-		// ReSharper disable once InconsistentNaming
-		public static void SupportTypeForAOT()
-		{
-			DecodeType( null );
-			DecodeList( null );
-			DecodeArray( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-			DecodeDictionary( null );
-		}
-
-		// ReSharper disable once InconsistentNaming
-		// ReSharper disable once UnusedMember.Local
-		static void SupportValueTypesForAOT()
-		{
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-			SupportTypeForAOT();
-		}
-	}
+// ReSharper disable once InconsistentNaming
+public static class JSON
+{
+    private static readonly Type includeAttrType = typeof(Include);
+    private static readonly Type excludeAttrType = typeof(Exclude);
+    private static readonly Type decodeAliasAttrType = typeof(DecodeAlias);
+
+    public static Variant Load(string json)
+    {
+        return string.IsNullOrEmpty(json) ? throw new ArgumentNullException("json") : Decoder.Decode(json);
+    }
+
+    public static string Dump(object data)
+    {
+        return Dump(data, EncodeOptions.None);
+    }
+
+    public static string Dump(object data, EncodeOptions options)
+    {
+        // Invoke methods tagged with [BeforeEncode] attribute.
+        if (data != null)
+        {
+            var type = data.GetType();
+            if (!(type.IsEnum || type.IsPrimitive || type.IsArray))
+            {
+                foreach (var method in type.GetMethods(instanceBindingFlags))
+                {
+                    if (method.GetCustomAttributes(false).AnyOfType(typeof(BeforeEncode)))
+                    {
+                        if (method.GetParameters().Length == 0)
+                        {
+                            method.Invoke(data, null);
+                        }
+                    }
+                }
+            }
+        }
+
+        return Encoder.Encode(data, options);
+    }
+
+    public static void MakeInto(Variant data, out T item)
+    {
+        item = DecodeType(data);
+    }
+
+    public static void Populate(Variant data, T item) where T : class
+    {
+        if (item == null)
+        {
+            throw new ArgumentNullException(nameof(item));
+        }
+        DecodeFields(data, ref item);
+    }
+
+    private static readonly Dictionary typeCache = [];
+
+    private static Type FindType(string fullName)
+    {
+        if (fullName == null)
+        {
+            return null;
+        }
+
+        Type type;
+        if (typeCache.TryGetValue(fullName, out type))
+        {
+            return type;
+        }
+
+        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
+        {
+            type = assembly.GetType(fullName);
+            if (type != null)
+            {
+                typeCache.Add(fullName, type);
+                return type;
+            }
+        }
+
+        return null;
+    }
+
+    private static T DecodeType(Variant data)
+    {
+        if (data == null)
+        {
+            return default;
+        }
+
+        var type = typeof(T);
+
+        var nulledType = Nullable.GetUnderlyingType(type);
+        if (nulledType != null)
+        {
+            var makeFunc = decodeTypeMethod.MakeGenericMethod(nulledType);
+            var v = makeFunc.Invoke(null, new object[] { data });
+            return (T)v;
+        }
+
+        if (type.IsEnum)
+        {
+            return (T)Enum.Parse(type, data.ToString(CultureInfo.InvariantCulture));
+        }
+
+        if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
+        {
+            return (T)Convert.ChangeType(data, type);
+        }
+
+        if (type == typeof(Guid))
+        {
+            return (T)(object)new Guid(data.ToString(CultureInfo.InvariantCulture));
+        }
+
+        if (type.IsArray)
+        {
+            if (type.GetArrayRank() == 1)
+            {
+                var makeFunc = decodeArrayMethod.MakeGenericMethod(type.GetElementType());
+                return (T)makeFunc.Invoke(null, new object[] { data });
+            }
+
+            if (data is not ProxyArray arrayData)
+            {
+                throw new DecodeException("Variant is expected to be a ProxyArray here, but it is not.");
+            }
+
+            var arrayRank = type.GetArrayRank();
+            var rankLengths = new int[arrayRank];
+            if (arrayData.CanBeMultiRankArray(rankLengths))
+            {
+                var elementType = type.GetElementType();
+                if (elementType == null)
+                {
+                    throw new DecodeException("Array element type is expected to be not null, but it is.");
+                }
+
+                var array = Array.CreateInstance(elementType, rankLengths);
+                var makeFunc = decodeMultiRankArrayMethod.MakeGenericMethod(elementType);
+                try
+                {
+                    makeFunc.Invoke(null, new object[] { arrayData, array, 1, rankLengths });
+                }
+                catch (Exception e)
+                {
+                    throw new DecodeException("Error decoding multidimensional array. Did you try to decode into an array of incompatible rank or element type?", e);
+                }
+
+                return (T)Convert.ChangeType(array, typeof(T));
+            }
+
+            throw new DecodeException("Error decoding multidimensional array; JSON data doesn't seem fit this structure.");
+        }
+
+        if (typeof(IList).IsAssignableFrom(type))
+        {
+            var makeFunc = decodeListMethod.MakeGenericMethod(type.GetGenericArguments());
+            return (T)makeFunc.Invoke(null, new object[] { data });
+        }
+
+        if (typeof(IDictionary).IsAssignableFrom(type))
+        {
+            var makeFunc = decodeDictionaryMethod.MakeGenericMethod(type.GetGenericArguments());
+            return (T)makeFunc.Invoke(null, new object[] { data });
+        }
+
+        // At this point we should be dealing with a class or struct.
+        T instance;
+        if (data is not ProxyObject proxyObject)
+        {
+            throw new InvalidCastException("ProxyObject expected when decoding into '" + type.FullName + "'.");
+        }
+
+        // If there's a type hint, use it to create the instance.
+        var typeHint = proxyObject.TypeHint;
+        if (typeHint != null && typeHint != type.FullName)
+        {
+            var makeType = FindType(typeHint);
+            if (makeType == null)
+            {
+                throw new TypeLoadException("Could not load type '" + typeHint + "'.");
+            }
+
+            if (type.IsAssignableFrom(makeType))
+            {
+                instance = (T)Activator.CreateInstance(makeType);
+                type = makeType;
+            }
+            else
+            {
+                throw new InvalidCastException("Cannot assign type '" + typeHint + "' to type '" + type.FullName + "'.");
+            }
+        }
+        else
+        {
+            // We don't have a type hint, so just instantiate the type we have.
+            instance = Activator.CreateInstance();
+        }
+
+        foreach (var pair in proxyObject)
+        {
+            var field = type.GetField(pair.Key, instanceBindingFlags);
+
+            // If the field doesn't exist, search through any [DecodeAlias] attributes.
+            if (field == null)
+            {
+                var fields = type.GetFields(instanceBindingFlags);
+                foreach (var fieldInfo in fields)
+                {
+                    foreach (var attribute in fieldInfo.GetCustomAttributes(true))
+                    {
+                        if (decodeAliasAttrType.IsInstanceOfType(attribute))
+                        {
+                            if (((DecodeAlias)attribute).Contains(pair.Key))
+                            {
+                                field = fieldInfo;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (field != null)
+            {
+                var shouldDecode = field.IsPublic;
+                foreach (var attribute in field.GetCustomAttributes(true))
+                {
+                    if (excludeAttrType.IsInstanceOfType(attribute))
+                    {
+                        shouldDecode = false;
+                    }
+
+                    if (includeAttrType.IsInstanceOfType(attribute))
+                    {
+                        shouldDecode = true;
+                    }
+                }
+
+                if (shouldDecode)
+                {
+                    var makeFunc = decodeTypeMethod.MakeGenericMethod(field.FieldType);
+                    if (type.IsValueType)
+                    {
+                        // Type is a struct.
+                        var instanceRef = (object)instance;
+                        field.SetValue(instanceRef, makeFunc.Invoke(null, new object[] { pair.Value }));
+                        instance = (T)instanceRef;
+                    }
+                    else
+                    {
+                        // Type is a class.
+                        field.SetValue(instance, makeFunc.Invoke(null, new object[] { pair.Value }));
+                    }
+                }
+            }
+
+            var property = type.GetProperty(pair.Key, instanceBindingFlags);
+
+            // If the property doesn't exist, search through any [DecodeAlias] attributes.
+            if (property == null)
+            {
+                var properties = type.GetProperties(instanceBindingFlags);
+                foreach (var propertyInfo in properties)
+                {
+                    foreach (var attribute in propertyInfo.GetCustomAttributes(false))
+                    {
+                        if (decodeAliasAttrType.IsInstanceOfType(attribute))
+                        {
+                            if (((DecodeAlias)attribute).Contains(pair.Key))
+                            {
+                                property = propertyInfo;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (property != null)
+            {
+                if (property.CanWrite && property.GetCustomAttributes(false).AnyOfType(includeAttrType))
+                {
+                    var makeFunc = decodeTypeMethod.MakeGenericMethod(new Type[] { property.PropertyType });
+                    if (type.IsValueType)
+                    {
+                        // Type is a struct.
+                        var instanceRef = (object)instance;
+                        property.SetValue(instanceRef, makeFunc.Invoke(null, new object[] { pair.Value }), null);
+                        instance = (T)instanceRef;
+                    }
+                    else
+                    {
+                        // Type is a class.
+                        property.SetValue(instance, makeFunc.Invoke(null, new object[] { pair.Value }), null);
+                    }
+                }
+            }
+        }
+
+        // Invoke methods tagged with [AfterDecode] attribute.
+        foreach (var method in type.GetMethods(instanceBindingFlags))
+        {
+            if (method.GetCustomAttributes(false).AnyOfType(typeof(AfterDecode)))
+            {
+                method.Invoke(instance, method.GetParameters().Length == 0 ? null : new object[] { data });
+            }
+        }
+
+        return instance;
+    }
+
+    private static void DecodeFields(Variant data, ref T instance)
+    {
+        var type = typeof(T);
+        if (data is not ProxyObject proxyObject)
+        {
+            throw new InvalidCastException("ProxyObject expected when decoding into '" + type.FullName + "'.");
+        }
+
+        foreach (var pair in proxyObject)
+        {
+            var field = type.GetField(pair.Key, instanceBindingFlags);
+
+            // If the field doesn't exist, search through any [DecodeAlias] attributes.
+            if (field == null)
+            {
+                var fields = type.GetFields(instanceBindingFlags);
+                foreach (var fieldInfo in fields)
+                {
+                    foreach (var attribute in fieldInfo.GetCustomAttributes(true))
+                    {
+                        if (decodeAliasAttrType.IsInstanceOfType(attribute))
+                        {
+                            if (((DecodeAlias)attribute).Contains(pair.Key))
+                            {
+                                field = fieldInfo;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (field != null)
+            {
+                var shouldDecode = field.IsPublic;
+                foreach (var attribute in field.GetCustomAttributes(true))
+                {
+                    if (excludeAttrType.IsInstanceOfType(attribute))
+                    {
+                        shouldDecode = false;
+                    }
+
+                    if (includeAttrType.IsInstanceOfType(attribute))
+                    {
+                        shouldDecode = true;
+                    }
+                }
+
+                if (shouldDecode)
+                {
+                    var makeFunc = decodeTypeMethod.MakeGenericMethod(field.FieldType);
+                    if (type.IsValueType)
+                    {
+                        // Type is a struct.
+                        var instanceRef = (object)instance;
+                        field.SetValue(instanceRef, makeFunc.Invoke(null, new object[] { pair.Value }));
+                        instance = (T)instanceRef;
+                    }
+                    else
+                    {
+                        // Type is a class.
+                        field.SetValue(instance, makeFunc.Invoke(null, new object[] { pair.Value }));
+                    }
+                }
+            }
+
+            var property = type.GetProperty(pair.Key, instanceBindingFlags);
+
+            // If the property doesn't exist, search through any [DecodeAlias] attributes.
+            if (property == null)
+            {
+                var properties = type.GetProperties(instanceBindingFlags);
+                foreach (var propertyInfo in properties)
+                {
+                    foreach (var attribute in propertyInfo.GetCustomAttributes(false))
+                    {
+                        if (decodeAliasAttrType.IsInstanceOfType(attribute))
+                        {
+                            if (((DecodeAlias)attribute).Contains(pair.Key))
+                            {
+                                property = propertyInfo;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (property != null)
+            {
+                if (property.CanWrite && property.GetCustomAttributes(false).AnyOfType(includeAttrType))
+                {
+                    var makeFunc = decodeTypeMethod.MakeGenericMethod(new Type[] { property.PropertyType });
+                    if (type.IsValueType)
+                    {
+                        // Type is a struct.
+                        var instanceRef = (object)instance;
+                        property.SetValue(instanceRef, makeFunc.Invoke(null, new object[] { pair.Value }), null);
+                        instance = (T)instanceRef;
+                    }
+                    else
+                    {
+                        // Type is a class.
+                        property.SetValue(instance, makeFunc.Invoke(null, new object[] { pair.Value }), null);
+                    }
+                }
+            }
+        }
+
+        // Invoke methods tagged with [AfterDecode] attribute.
+        foreach (var method in type.GetMethods(instanceBindingFlags))
+        {
+            if (method.GetCustomAttributes(false).AnyOfType(typeof(AfterDecode)))
+            {
+                method.Invoke(instance, method.GetParameters().Length == 0 ? null : new object[] { data });
+            }
+        }
+    }
+
+    // ReSharper disable once UnusedMethodReturnValue.Local
+    private static List DecodeList(Variant data)
+    {
+        var list = new List();
+
+        if (data is not ProxyArray proxyArray)
+        {
+            throw new DecodeException("Variant is expected to be a ProxyArray here, but it is not.");
+        }
+
+        foreach (var item in proxyArray)
+        {
+            list.Add(DecodeType(item));
+        }
+
+        return list;
+    }
+
+    // ReSharper disable once UnusedMethodReturnValue.Local
+    private static Dictionary DecodeDictionary(Variant data)
+    {
+        var dict = new Dictionary();
+        var type = typeof(TKey);
+
+        if (data is not ProxyObject proxyObject)
+        {
+            throw new DecodeException("Variant is expected to be a ProxyObject here, but it is not.");
+        }
+
+        foreach (var pair in proxyObject)
+        {
+            var k = (TKey)(type.IsEnum ? Enum.Parse(type, pair.Key) : Convert.ChangeType(pair.Key, type));
+            var v = DecodeType(pair.Value);
+            dict.Add(k, v);
+        }
+
+        return dict;
+    }
+
+    // ReSharper disable once UnusedMethodReturnValue.Local
+    private static T[] DecodeArray(Variant data)
+    {
+        if (data is not ProxyArray arrayData)
+        {
+            throw new DecodeException("Variant is expected to be a ProxyArray here, but it is not.");
+        }
+
+        var arraySize = arrayData.Count;
+        var array = new T[arraySize];
+
+        var i = 0;
+        foreach (var item in arrayData)
+        {
+            array[i++] = DecodeType(item);
+        }
+
+        return array;
+    }
+
+    // ReSharper disable once UnusedMember.Local
+    private static void DecodeMultiRankArray(ProxyArray arrayData, Array array, int arrayRank, int[] indices)
+    {
+        var count = arrayData.Count;
+        for (var i = 0; i < count; i++)
+        {
+            indices[arrayRank - 1] = i;
+            if (arrayRank < array.Rank)
+            {
+                DecodeMultiRankArray(arrayData[i] as ProxyArray, array, arrayRank + 1, indices);
+            }
+            else
+            {
+                array.SetValue(DecodeType(arrayData[i]), indices);
+            }
+        }
+    }
+
+    private const BindingFlags instanceBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
+    private const BindingFlags staticBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
+    private static readonly MethodInfo decodeTypeMethod = typeof(JSON).GetMethod("DecodeType", staticBindingFlags);
+    private static readonly MethodInfo decodeListMethod = typeof(JSON).GetMethod("DecodeList", staticBindingFlags);
+    private static readonly MethodInfo decodeDictionaryMethod = typeof(JSON).GetMethod("DecodeDictionary", staticBindingFlags);
+    private static readonly MethodInfo decodeArrayMethod = typeof(JSON).GetMethod("DecodeArray", staticBindingFlags);
+    private static readonly MethodInfo decodeMultiRankArrayMethod = typeof(JSON).GetMethod("DecodeMultiRankArray", staticBindingFlags);
+
+    // ReSharper disable once InconsistentNaming
+    public static void SupportTypeForAOT()
+    {
+        DecodeType(null);
+        DecodeList(null);
+        DecodeArray(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+        DecodeDictionary(null);
+    }
+
+    // ReSharper disable once InconsistentNaming
+    // ReSharper disable once UnusedMember.Local
+    private static void SupportValueTypesForAOT()
+    {
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+        SupportTypeForAOT();
+    }
 }
diff --git a/MelonLoader/TinyJSON/ProxyArray.cs b/MelonLoader/TinyJSON/ProxyArray.cs
index 3a053eec5..9f937c7f1 100644
--- a/MelonLoader/TinyJSON/ProxyArray.cs
+++ b/MelonLoader/TinyJSON/ProxyArray.cs
@@ -1,104 +1,92 @@
 using System.Collections;
 using System.Collections.Generic;
 
-namespace MelonLoader.TinyJSON
-{
-	public sealed class ProxyArray : Variant, IEnumerable
-	{
-		readonly List list;
-
-
-		public ProxyArray()
-		{
-			list = new List();
-		}
-
-
-		IEnumerator IEnumerable.GetEnumerator()
-		{
-			return list.GetEnumerator();
-		}
-
-
-		IEnumerator IEnumerable.GetEnumerator()
-		{
-			return list.GetEnumerator();
-		}
-
-
-		public void Add( Variant item )
-		{
-			list.Add( item );
-		}
-
-
-		public override Variant this[ int index ]
-		{
-			get
-			{
-				return list[index];
-			}
-			set
-			{
-				list[index] = value;
-			}
-		}
-
-
-		public int Count
-		{
-			get
-			{
-				return list.Count;
-			}
-		}
+namespace MelonLoader.TinyJSON;
 
-
-		internal bool CanBeMultiRankArray( int[] rankLengths )
-		{
-			return CanBeMultiRankArray( 0, rankLengths );
-		}
-
-
-		bool CanBeMultiRankArray( int rank, int[] rankLengths )
-		{
-			var count = list.Count;
-			rankLengths[rank] = count;
-
-			if (rank == rankLengths.Length - 1)
-			{
-				return true;
-			}
-
-			var firstItem = list[0] as ProxyArray;
-			if (firstItem == null)
-			{
-				return false;
-			}
-
-			var firstItemCount = firstItem.Count;
-
-			for (var i = 1; i < count; i++)
-			{
-				var item = list[i] as ProxyArray;
-
-				if (item == null)
-				{
-					return false;
-				}
-
-				if (item.Count != firstItemCount)
-				{
-					return false;
-				}
-
-				if (!item.CanBeMultiRankArray( rank + 1, rankLengths ))
-				{
-					return false;
-				}
-			}
-
-			return true;
-		}
-	}
+public sealed class ProxyArray : Variant, IEnumerable
+{
+    private readonly List list;
+
+    public ProxyArray()
+    {
+        list = [];
+    }
+
+    IEnumerator IEnumerable.GetEnumerator()
+    {
+        return list.GetEnumerator();
+    }
+
+    IEnumerator IEnumerable.GetEnumerator()
+    {
+        return list.GetEnumerator();
+    }
+
+    public void Add(Variant item)
+    {
+        list.Add(item);
+    }
+
+    public override Variant this[int index]
+    {
+        get
+        {
+            return list[index];
+        }
+        set
+        {
+            list[index] = value;
+        }
+    }
+
+    public int Count
+    {
+        get
+        {
+            return list.Count;
+        }
+    }
+
+    internal bool CanBeMultiRankArray(int[] rankLengths)
+    {
+        return CanBeMultiRankArray(0, rankLengths);
+    }
+
+    private bool CanBeMultiRankArray(int rank, int[] rankLengths)
+    {
+        var count = list.Count;
+        rankLengths[rank] = count;
+
+        if (rank == rankLengths.Length - 1)
+        {
+            return true;
+        }
+
+        if (list[0] is not ProxyArray firstItem)
+        {
+            return false;
+        }
+
+        var firstItemCount = firstItem.Count;
+
+        for (var i = 1; i < count; i++)
+        {
+            if (list[i] is not ProxyArray item)
+            {
+                return false;
+            }
+
+            if (item.Count != firstItemCount)
+            {
+                return false;
+            }
+
+            if (!item.CanBeMultiRankArray(rank + 1, rankLengths))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/MelonLoader/TinyJSON/ProxyBoolean.cs b/MelonLoader/TinyJSON/ProxyBoolean.cs
index b4d07abcc..e1193022f 100644
--- a/MelonLoader/TinyJSON/ProxyBoolean.cs
+++ b/MelonLoader/TinyJSON/ProxyBoolean.cs
@@ -1,27 +1,23 @@
 using System;
 
-namespace MelonLoader.TinyJSON
-{
-	public sealed class ProxyBoolean : Variant
-	{
-		readonly bool value;
-
-
-		public ProxyBoolean( bool value )
-		{
-			this.value = value;
-		}
+namespace MelonLoader.TinyJSON;
 
+public sealed class ProxyBoolean : Variant
+{
+    private readonly bool value;
 
-		public override bool ToBoolean( IFormatProvider provider )
-		{
-			return value;
-		}
+    public ProxyBoolean(bool value)
+    {
+        this.value = value;
+    }
 
+    public override bool ToBoolean(IFormatProvider provider)
+    {
+        return value;
+    }
 
-		public override string ToString( IFormatProvider provider )
-		{
-			return value ? "true" : "false";
-		}
-	}
+    public override string ToString(IFormatProvider provider)
+    {
+        return value ? "true" : "false";
+    }
 }
diff --git a/MelonLoader/TinyJSON/ProxyNumber.cs b/MelonLoader/TinyJSON/ProxyNumber.cs
index af80ad3d7..5f21ce383 100644
--- a/MelonLoader/TinyJSON/ProxyNumber.cs
+++ b/MelonLoader/TinyJSON/ProxyNumber.cs
@@ -1,153 +1,130 @@
 using System;
 using System.Globalization;
 
-namespace MelonLoader.TinyJSON
-{
-	public sealed class ProxyNumber : Variant
-	{
-		static readonly char[] floatingPointCharacters = { '.', 'e' };
-		readonly IConvertible value;
-
-
-		public ProxyNumber( IConvertible value )
-		{
-			var stringValue = value as string;
-			this.value = stringValue != null ? Parse( stringValue ) : value;
-		}
-
-
-		static IConvertible Parse( string value )
-		{
-			if (value.IndexOfAny( floatingPointCharacters ) == -1)
-			{
-				if (value[0] == '-')
-				{
-					Int64 parsedValue;
-					if (Int64.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue ))
-					{
-						return parsedValue;
-					}
-				}
-				else
-				{
-					UInt64 parsedValue;
-					if (UInt64.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue ))
-					{
-						return parsedValue;
-					}
-				}
-			}
-
-			Decimal decimalValue;
-			if (Decimal.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out decimalValue ))
-			{
-				// Check for decimal underflow.
-				if (decimalValue == Decimal.Zero)
-				{
-					Double parsedValue;
-					if (Double.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue ))
-					{
-						if (Math.Abs( parsedValue ) > Double.Epsilon)
-						{
-							return parsedValue;
-						}
-					}
-				}
-
-				return decimalValue;
-			}
-
-			Double doubleValue;
-			if (Double.TryParse( value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out doubleValue ))
-			{
-				return doubleValue;
-			}
-
-			return 0;
-		}
-
-
-		public override bool ToBoolean( IFormatProvider provider )
-		{
-			return value.ToBoolean( provider );
-		}
-
-
-		public override byte ToByte( IFormatProvider provider )
-		{
-			return value.ToByte( provider );
-		}
-
-
-		public override char ToChar( IFormatProvider provider )
-		{
-			return value.ToChar( provider );
-		}
-
-
-		public override decimal ToDecimal( IFormatProvider provider )
-		{
-			return value.ToDecimal( provider );
-		}
-
-
-		public override double ToDouble( IFormatProvider provider )
-		{
-			return value.ToDouble( provider );
-		}
-
-
-		public override short ToInt16( IFormatProvider provider )
-		{
-			return value.ToInt16( provider );
-		}
+namespace MelonLoader.TinyJSON;
 
-
-		public override int ToInt32( IFormatProvider provider )
-		{
-			return value.ToInt32( provider );
-		}
-
-
-		public override long ToInt64( IFormatProvider provider )
-		{
-			return value.ToInt64( provider );
-		}
-
-
-		public override sbyte ToSByte( IFormatProvider provider )
-		{
-			return value.ToSByte( provider );
-		}
-
-
-		public override float ToSingle( IFormatProvider provider )
-		{
-			return value.ToSingle( provider );
-		}
-
-
-		public override string ToString( IFormatProvider provider )
-		{
-			return value.ToString( provider );
-		}
-
-
-		public override ushort ToUInt16( IFormatProvider provider )
-		{
-			return value.ToUInt16( provider );
-		}
-
-
-		public override uint ToUInt32( IFormatProvider provider )
-		{
-			return value.ToUInt32( provider );
-		}
-
-
-		public override ulong ToUInt64( IFormatProvider provider )
-		{
-			return value.ToUInt64( provider );
-		}
-	}
+public sealed class ProxyNumber : Variant
+{
+    private static readonly char[] floatingPointCharacters = { '.', 'e' };
+    private readonly IConvertible value;
+
+    public ProxyNumber(IConvertible value)
+    {
+        this.value = value is string stringValue ? Parse(stringValue) : value;
+    }
+
+    private static IConvertible Parse(string value)
+    {
+        if (value.IndexOfAny(floatingPointCharacters) == -1)
+        {
+            if (value[0] == '-')
+            {
+                long parsedValue;
+                if (long.TryParse(value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue))
+                {
+                    return parsedValue;
+                }
+            }
+            else
+            {
+                ulong parsedValue;
+                if (ulong.TryParse(value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue))
+                {
+                    return parsedValue;
+                }
+            }
+        }
+
+        decimal decimalValue;
+        if (decimal.TryParse(value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out decimalValue))
+        {
+            // Check for decimal underflow.
+            if (decimalValue == decimal.Zero)
+            {
+                double parsedValue;
+                if (double.TryParse(value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out parsedValue))
+                {
+                    if (Math.Abs(parsedValue) > double.Epsilon)
+                    {
+                        return parsedValue;
+                    }
+                }
+            }
+
+            return decimalValue;
+        }
+
+        double doubleValue;
+        return double.TryParse(value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out doubleValue) ? doubleValue : (IConvertible)0;
+    }
+
+    public override bool ToBoolean(IFormatProvider provider)
+    {
+        return value.ToBoolean(provider);
+    }
+
+    public override byte ToByte(IFormatProvider provider)
+    {
+        return value.ToByte(provider);
+    }
+
+    public override char ToChar(IFormatProvider provider)
+    {
+        return value.ToChar(provider);
+    }
+
+    public override decimal ToDecimal(IFormatProvider provider)
+    {
+        return value.ToDecimal(provider);
+    }
+
+    public override double ToDouble(IFormatProvider provider)
+    {
+        return value.ToDouble(provider);
+    }
+
+    public override short ToInt16(IFormatProvider provider)
+    {
+        return value.ToInt16(provider);
+    }
+
+    public override int ToInt32(IFormatProvider provider)
+    {
+        return value.ToInt32(provider);
+    }
+
+    public override long ToInt64(IFormatProvider provider)
+    {
+        return value.ToInt64(provider);
+    }
+
+    public override sbyte ToSByte(IFormatProvider provider)
+    {
+        return value.ToSByte(provider);
+    }
+
+    public override float ToSingle(IFormatProvider provider)
+    {
+        return value.ToSingle(provider);
+    }
+
+    public override string ToString(IFormatProvider provider)
+    {
+        return value.ToString(provider);
+    }
+
+    public override ushort ToUInt16(IFormatProvider provider)
+    {
+        return value.ToUInt16(provider);
+    }
+
+    public override uint ToUInt32(IFormatProvider provider)
+    {
+        return value.ToUInt32(provider);
+    }
+
+    public override ulong ToUInt64(IFormatProvider provider)
+    {
+        return value.ToUInt64(provider);
+    }
 }
diff --git a/MelonLoader/TinyJSON/ProxyObject.cs b/MelonLoader/TinyJSON/ProxyObject.cs
index b94c66452..26b7e8fb0 100644
--- a/MelonLoader/TinyJSON/ProxyObject.cs
+++ b/MelonLoader/TinyJSON/ProxyObject.cs
@@ -2,97 +2,81 @@
 using System.Collections.Generic;
 using System.Globalization;
 
-namespace MelonLoader.TinyJSON
-{
-	public sealed class ProxyObject : Variant, IEnumerable>
-	{
-		public const string TypeHintKey = "@type";
-		readonly Dictionary dict;
-
-
-		public ProxyObject()
-		{
-			dict = new Dictionary();
-		}
-
-
-		IEnumerator> IEnumerable>.GetEnumerator()
-		{
-			return dict.GetEnumerator();
-		}
-
-
-		IEnumerator IEnumerable.GetEnumerator()
-		{
-			return dict.GetEnumerator();
-		}
-
-
-		public void Add( string key, Variant item )
-		{
-			dict.Add( key, item );
-		}
-
-
-		public bool TryGetValue( string key, out Variant item )
-		{
-			return dict.TryGetValue( key, out item );
-		}
+namespace MelonLoader.TinyJSON;
 
-
-		public string TypeHint
-		{
-			get
-			{
-				Variant item;
-				if (TryGetValue( TypeHintKey, out item ))
-				{
-					return item.ToString( CultureInfo.InvariantCulture );
-				}
-
-				return null;
-			}
-		}
-
-
-		public override Variant this[ string key ]
-		{
-			get
-			{
-				return dict[key];
-			}
-			set
-			{
-				dict[key] = value;
-			}
-		}
-
-
-		public int Count
-		{
-			get
-			{
-				return dict.Count;
-			}
-		}
-
-
-		public Dictionary.KeyCollection Keys
-		{
-			get
-			{
-				return dict.Keys;
-			}
-		}
-
-
-		// ReSharper disable once UnusedMember.Global
-		public Dictionary.ValueCollection Values
-		{
-			get
-			{
-				return dict.Values;
-			}
-		}
-	}
+public sealed class ProxyObject : Variant, IEnumerable>
+{
+    public const string TypeHintKey = "@type";
+    private readonly Dictionary dict;
+
+    public ProxyObject()
+    {
+        dict = [];
+    }
+
+    IEnumerator> IEnumerable>.GetEnumerator()
+    {
+        return dict.GetEnumerator();
+    }
+
+    IEnumerator IEnumerable.GetEnumerator()
+    {
+        return dict.GetEnumerator();
+    }
+
+    public void Add(string key, Variant item)
+    {
+        dict.Add(key, item);
+    }
+
+    public bool TryGetValue(string key, out Variant item)
+    {
+        return dict.TryGetValue(key, out item);
+    }
+
+    public string TypeHint
+    {
+        get
+        {
+            Variant item;
+            return TryGetValue(TypeHintKey, out item) ? item.ToString(CultureInfo.InvariantCulture) : null;
+        }
+    }
+
+    public override Variant this[string key]
+    {
+        get
+        {
+            return dict[key];
+        }
+        set
+        {
+            dict[key] = value;
+        }
+    }
+
+    public int Count
+    {
+        get
+        {
+            return dict.Count;
+        }
+    }
+
+    public Dictionary.KeyCollection Keys
+    {
+        get
+        {
+            return dict.Keys;
+        }
+    }
+
+    // ReSharper disable once UnusedMember.Global
+    public Dictionary.ValueCollection Values
+    {
+        get
+        {
+            return dict.Values;
+        }
+    }
 }
diff --git a/MelonLoader/TinyJSON/ProxyString.cs b/MelonLoader/TinyJSON/ProxyString.cs
index 48003cd32..b29da1241 100644
--- a/MelonLoader/TinyJSON/ProxyString.cs
+++ b/MelonLoader/TinyJSON/ProxyString.cs
@@ -1,21 +1,18 @@
 using System;
 
-namespace MelonLoader.TinyJSON
-{
-	public sealed class ProxyString : Variant
-	{
-		readonly string value;
-
+namespace MelonLoader.TinyJSON;
 
-		public ProxyString( string value )
-		{
-			this.value = value;
-		}
+public sealed class ProxyString : Variant
+{
+    private readonly string value;
 
+    public ProxyString(string value)
+    {
+        this.value = value;
+    }
 
-		public override string ToString( IFormatProvider provider )
-		{
-			return value;
-		}
-	}
+    public override string ToString(IFormatProvider provider)
+    {
+        return value;
+    }
 }
diff --git a/MelonLoader/TinyJSON/Variant.cs b/MelonLoader/TinyJSON/Variant.cs
index 17738a1e1..cf7c9aa22 100644
--- a/MelonLoader/TinyJSON/Variant.cs
+++ b/MelonLoader/TinyJSON/Variant.cs
@@ -1,250 +1,214 @@
 using System;
 using System.Globalization;
 
-namespace MelonLoader.TinyJSON
-{
-	public abstract class Variant : IConvertible
-	{
-		protected static readonly IFormatProvider FormatProvider = new NumberFormatInfo();
-
-
-		// ReSharper disable once UnusedMember.Global
-		public void Make( out T item )
-		{
-			JSON.MakeInto( this, out item );
-		}
-
-
-		public T Make()
-		{
-			T item;
-			JSON.MakeInto( this, out item );
-			return item;
-		}
-
-
-		public void Populate( T item ) where T : class
-		{
-			JSON.Populate( this, item );
-		}
-
-
-		// ReSharper disable once InconsistentNaming
-		// ReSharper disable once UnusedMember.Global
-		public string ToJSON()
-		{
-			return JSON.Dump( this );
-		}
-
-
-		public virtual TypeCode GetTypeCode()
-		{
-			return TypeCode.Object;
-		}
-
-
-		public virtual object ToType( Type conversionType, IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to " + conversionType.Name );
-		}
-
-
-		public virtual DateTime ToDateTime( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to DateTime" );
-		}
-
-
-		public virtual bool ToBoolean( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Boolean" );
-		}
-
-		public virtual byte ToByte( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Byte" );
-		}
-
-
-		public virtual char ToChar( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Char" );
-		}
-
-
-		public virtual decimal ToDecimal( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Decimal" );
-		}
-
-
-		public virtual double ToDouble( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Double" );
-		}
-
-
-		public virtual short ToInt16( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Int16" );
-		}
-
-
-		public virtual int ToInt32( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Int32" );
-		}
-
-
-		public virtual long ToInt64( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Int64" );
-		}
-
-
-		public virtual sbyte ToSByte( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to SByte" );
-		}
-
-
-		public virtual float ToSingle( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to Single" );
-		}
-
-
-		public virtual string ToString( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to String" );
-		}
-
+namespace MelonLoader.TinyJSON;
 
-		public virtual ushort ToUInt16( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to UInt16" );
-		}
-
-
-		public virtual uint ToUInt32( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to UInt32" );
-		}
-
-
-		public virtual ulong ToUInt64( IFormatProvider provider )
-		{
-			throw new InvalidCastException( "Cannot convert " + GetType() + " to UInt64" );
-		}
-
-
-		public override string ToString()
-		{
-			return ToString( FormatProvider );
-		}
-
-
-		// ReSharper disable once UnusedMemberInSuper.Global
-		public virtual Variant this[ string key ]
-		{
-			get
-			{
-				throw new NotSupportedException();
-			}
-
-			// ReSharper disable once UnusedMember.Global
-			set
-			{
-				throw new NotSupportedException();
-			}
-		}
-
-
-		// ReSharper disable once UnusedMemberInSuper.Global
-		public virtual Variant this[ int index ]
-		{
-			get
-			{
-				throw new NotSupportedException();
-			}
-
-			// ReSharper disable once UnusedMember.Global
-			set
-			{
-				throw new NotSupportedException();
-			}
-		}
-
-
-		public static implicit operator Boolean( Variant variant )
-		{
-			return variant.ToBoolean( FormatProvider );
-		}
-
-
-		public static implicit operator Single( Variant variant )
-		{
-			return variant.ToSingle( FormatProvider );
-		}
-
-
-		public static implicit operator Double( Variant variant )
-		{
-			return variant.ToDouble( FormatProvider );
-		}
-
-
-		public static implicit operator UInt16( Variant variant )
-		{
-			return variant.ToUInt16( FormatProvider );
-		}
-
-
-		public static implicit operator Int16( Variant variant )
-		{
-			return variant.ToInt16( FormatProvider );
-		}
-
-
-		public static implicit operator UInt32( Variant variant )
-		{
-			return variant.ToUInt32( FormatProvider );
-		}
-
-
-		public static implicit operator Int32( Variant variant )
-		{
-			return variant.ToInt32( FormatProvider );
-		}
-
-
-		public static implicit operator UInt64( Variant variant )
-		{
-			return variant.ToUInt64( FormatProvider );
-		}
-
-
-		public static implicit operator Int64( Variant variant )
-		{
-			return variant.ToInt64( FormatProvider );
-		}
-
-
-		public static implicit operator Decimal( Variant variant )
-		{
-			return variant.ToDecimal( FormatProvider );
-		}
-
-
-		public static implicit operator String( Variant variant )
-		{
-			return variant.ToString( FormatProvider );
-		}
-
-
-		public static implicit operator Guid( Variant variant )
-		{
-			return new Guid( variant.ToString( FormatProvider ) );
-		}
-	}
+public abstract class Variant : IConvertible
+{
+    protected static readonly IFormatProvider FormatProvider = new NumberFormatInfo();
+
+    // ReSharper disable once UnusedMember.Global
+    public void Make(out T item)
+    {
+        JSON.MakeInto(this, out item);
+    }
+
+    public T Make()
+    {
+        T item;
+        JSON.MakeInto(this, out item);
+        return item;
+    }
+
+    public void Populate(T item) where T : class
+    {
+        JSON.Populate(this, item);
+    }
+
+    // ReSharper disable once InconsistentNaming
+    // ReSharper disable once UnusedMember.Global
+    public string ToJSON()
+    {
+        return JSON.Dump(this);
+    }
+
+    public virtual TypeCode GetTypeCode()
+    {
+        return TypeCode.Object;
+    }
+
+    public virtual object ToType(Type conversionType, IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to " + conversionType.Name);
+    }
+
+    public virtual DateTime ToDateTime(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to DateTime");
+    }
+
+    public virtual bool ToBoolean(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Boolean");
+    }
+
+    public virtual byte ToByte(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Byte");
+    }
+
+    public virtual char ToChar(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Char");
+    }
+
+    public virtual decimal ToDecimal(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Decimal");
+    }
+
+    public virtual double ToDouble(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Double");
+    }
+
+    public virtual short ToInt16(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Int16");
+    }
+
+    public virtual int ToInt32(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Int32");
+    }
+
+    public virtual long ToInt64(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Int64");
+    }
+
+    public virtual sbyte ToSByte(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to SByte");
+    }
+
+    public virtual float ToSingle(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to Single");
+    }
+
+    public virtual string ToString(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to String");
+    }
+
+    public virtual ushort ToUInt16(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to UInt16");
+    }
+
+    public virtual uint ToUInt32(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to UInt32");
+    }
+
+    public virtual ulong ToUInt64(IFormatProvider provider)
+    {
+        throw new InvalidCastException("Cannot convert " + GetType() + " to UInt64");
+    }
+
+    public override string ToString()
+    {
+        return ToString(FormatProvider);
+    }
+
+    // ReSharper disable once UnusedMemberInSuper.Global
+    public virtual Variant this[string key]
+    {
+        get
+        {
+            throw new NotSupportedException();
+        }
+
+        // ReSharper disable once UnusedMember.Global
+        set
+        {
+            throw new NotSupportedException();
+        }
+    }
+
+    // ReSharper disable once UnusedMemberInSuper.Global
+    public virtual Variant this[int index]
+    {
+        get
+        {
+            throw new NotSupportedException();
+        }
+
+        // ReSharper disable once UnusedMember.Global
+        set
+        {
+            throw new NotSupportedException();
+        }
+    }
+
+    public static implicit operator bool(Variant variant)
+    {
+        return variant.ToBoolean(FormatProvider);
+    }
+
+    public static implicit operator float(Variant variant)
+    {
+        return variant.ToSingle(FormatProvider);
+    }
+
+    public static implicit operator double(Variant variant)
+    {
+        return variant.ToDouble(FormatProvider);
+    }
+
+    public static implicit operator ushort(Variant variant)
+    {
+        return variant.ToUInt16(FormatProvider);
+    }
+
+    public static implicit operator short(Variant variant)
+    {
+        return variant.ToInt16(FormatProvider);
+    }
+
+    public static implicit operator uint(Variant variant)
+    {
+        return variant.ToUInt32(FormatProvider);
+    }
+
+    public static implicit operator int(Variant variant)
+    {
+        return variant.ToInt32(FormatProvider);
+    }
+
+    public static implicit operator ulong(Variant variant)
+    {
+        return variant.ToUInt64(FormatProvider);
+    }
+
+    public static implicit operator long(Variant variant)
+    {
+        return variant.ToInt64(FormatProvider);
+    }
+
+    public static implicit operator decimal(Variant variant)
+    {
+        return variant.ToDecimal(FormatProvider);
+    }
+
+    public static implicit operator string(Variant variant)
+    {
+        return variant.ToString(FormatProvider);
+    }
+
+    public static implicit operator Guid(Variant variant)
+    {
+        return new Guid(variant.ToString(FormatProvider));
+    }
 }
diff --git a/MelonLoader/TomlMapper.cs b/MelonLoader/TomlMapper.cs
index adb68a2b4..1e391d862 100644
--- a/MelonLoader/TomlMapper.cs
+++ b/MelonLoader/TomlMapper.cs
@@ -2,34 +2,31 @@
 using Tomlet;
 using Tomlet.Models;
 
-namespace MelonLoader
+namespace MelonLoader;
+
+public class TomlMapper
 {
-    public class TomlMapper
-    {
-        static TomlMapper()
-             => TomletMain.RegisterMapper(WriteLemonTupleInt, ReadLemonTupleInt);
+    static TomlMapper()
+         => TomletMain.RegisterMapper(WriteLemonTupleInt, ReadLemonTupleInt);
 
-        public T[] ReadArray(TomlValue value) => TomletMain.To(value);
-        public TomlArray WriteArray(T[] value) => (TomlArray)TomletMain.ValueFrom(value);
+    public T[] ReadArray(TomlValue value) => TomletMain.To(value);
+    public TomlArray WriteArray(T[] value) => (TomlArray)TomletMain.ValueFrom(value);
 
-        public List ReadList(TomlValue value) => TomletMain.To>(value);
-        public TomlArray WriteList(List value) => (TomlArray)TomletMain.ValueFrom(value);
+    public List ReadList(TomlValue value) => TomletMain.To>(value);
+    public TomlArray WriteList(List value) => (TomlArray)TomletMain.ValueFrom(value);
 
-        public TomlValue ToToml(T value) => TomletMain.ValueFrom(value);
-        public T FromToml(TomlValue value) => TomletMain.To(value);
+    public TomlValue ToToml(T value) => TomletMain.ValueFrom(value);
+    public T FromToml(TomlValue value) => TomletMain.To(value);
 
-        private static TomlValue WriteLemonTupleInt(LemonTuple value)
-        {
-            int[] ints = new[] { value.Item1, value.Item2 };
-            return MelonPreferences.Mapper.WriteArray(ints);
-        }
+    private static TomlValue WriteLemonTupleInt(LemonTuple value)
+    {
+        var ints = new[] { value.Item1, value.Item2 };
+        return MelonPreferences.Mapper.WriteArray(ints);
+    }
 
-        private static LemonTuple ReadLemonTupleInt(TomlValue value)
-        {
-            int[] ints = MelonPreferences.Mapper.ReadArray(value);
-            if (ints == null || ints.Length != 2)
-                return default;
-            return new LemonTuple() { Item1 = ints[0], Item2 = ints[1] };
-        }
+    private static LemonTuple ReadLemonTupleInt(TomlValue value)
+    {
+        var ints = MelonPreferences.Mapper.ReadArray(value);
+        return ints == null || ints.Length != 2 ? default : new LemonTuple() { Item1 = ints[0], Item2 = ints[1] };
     }
 }
diff --git a/MelonLoader/Utils/Assertion.cs b/MelonLoader/Utils/Assertion.cs
index 5fdb739a7..3278ee091 100644
--- a/MelonLoader/Utils/Assertion.cs
+++ b/MelonLoader/Utils/Assertion.cs
@@ -1,31 +1,29 @@
 using System;
-using System.Diagnostics;
 using System.Runtime.InteropServices;
 
-namespace MelonLoader.Utils
+namespace MelonLoader.Utils;
+
+internal static class Assertion
 {
-    internal static class Assertion
-    {
-        internal static bool ShouldContinue = true;
+    internal static bool ShouldContinue = true;
 
-        //TODO: Could this be done in a better way? net35/6 load PresentationFramework differently so I could not rely on it
-        //This crashes with start screen enabled
-        [DllImport("user32.dll", CharSet = CharSet.Auto)]
-        internal static extern IntPtr MessageBox(int hWnd, String text, String caption, uint type);
+    //TODO: Could this be done in a better way? net35/6 load PresentationFramework differently so I could not rely on it
+    //This crashes with start screen enabled
+    [DllImport("user32.dll", CharSet = CharSet.Auto)]
+    internal static extern IntPtr MessageBox(int hWnd, string text, string caption, uint type);
 
-        internal static void ThrowInternalFailure(string msg)
-        {
-            if (!ShouldContinue)
-                return;
+    internal static void ThrowInternalFailure(string msg)
+    {
+        if (!ShouldContinue)
+            return;
 
-            ShouldContinue = false;
+        ShouldContinue = false;
 
-            MelonLogger.PassLogError(msg, "INTERNAL FAILURE", false);
+        MelonLogger.PassLogError(msg, "INTERNAL FAILURE", false);
 
-            string caption = "INTERNAL FAILURE!";
-            var result = MessageBox(0, msg, caption, 0);
-            while (result == IntPtr.Zero)
-                Environment.Exit(1);
-        }
+        var caption = "INTERNAL FAILURE!";
+        var result = MessageBox(0, msg, caption, 0);
+        while (result == IntPtr.Zero)
+            Environment.Exit(1);
     }
 }
\ No newline at end of file
diff --git a/MelonLoader/Utils/LoggerUtils.cs b/MelonLoader/Utils/LoggerUtils.cs
index c0e4e4b52..17ea5882f 100644
--- a/MelonLoader/Utils/LoggerUtils.cs
+++ b/MelonLoader/Utils/LoggerUtils.cs
@@ -2,63 +2,56 @@
 using System.Collections.Generic;
 using System.Drawing;
 
-namespace MelonLoader.Utils
+namespace MelonLoader.Utils;
+
+internal static class LoggerUtils
 {
-    internal static class LoggerUtils
+    internal static Dictionary ConsoleColorDict = new()
     {
-        internal static Dictionary ConsoleColorDict = new Dictionary
-        {
-            { ConsoleColor.Black, Color.Black },
-            { ConsoleColor.DarkBlue, Color.DarkBlue },
-            { ConsoleColor.DarkGreen, Color.DarkGreen },
-            { ConsoleColor.DarkCyan, Color.DarkCyan },
-            { ConsoleColor.DarkRed, Color.DarkRed },
-            { ConsoleColor.DarkMagenta, Color.DarkMagenta },
-            { ConsoleColor.DarkYellow, Color.Yellow },
-            { ConsoleColor.Gray, Color.LightGray },
-            { ConsoleColor.DarkGray, Color.DarkGray },
-            { ConsoleColor.Blue, Color.CornflowerBlue } ,
-            { ConsoleColor.Green, Color.LimeGreen },
-            { ConsoleColor.Cyan, Color.Cyan },
-            { ConsoleColor.Red, Color.IndianRed },
-            { ConsoleColor.Magenta, Color.Magenta },
-            { ConsoleColor.Yellow, Color.Yellow },
-            { ConsoleColor.White, Color.White },
-        };
-
-        internal static Dictionary DrawingColorDict = new Dictionary
-        {
-            { Color.Black, ConsoleColor.Black },
-            { Color.DarkBlue, ConsoleColor.DarkBlue },
-            { Color.DarkGreen, ConsoleColor.DarkGreen },
-            { Color.DarkCyan, ConsoleColor.DarkCyan },
-            { Color.DarkRed, ConsoleColor.DarkRed },
-            { Color.DarkMagenta, ConsoleColor.DarkMagenta },
-            { Color.Yellow, ConsoleColor.Yellow },
-            { Color.LightGray, ConsoleColor.Gray },
-            { Color.DarkGray, ConsoleColor.DarkGray },
-            { Color.CornflowerBlue, ConsoleColor.Blue } ,
-            { Color.LimeGreen, ConsoleColor.Green },
-            { Color.Cyan, ConsoleColor.Cyan },
-            { Color.IndianRed, ConsoleColor.Red },
-            { Color.Magenta, ConsoleColor.Magenta },
-            { Color.White, ConsoleColor.White },
-        };
+        { ConsoleColor.Black, Color.Black },
+        { ConsoleColor.DarkBlue, Color.DarkBlue },
+        { ConsoleColor.DarkGreen, Color.DarkGreen },
+        { ConsoleColor.DarkCyan, Color.DarkCyan },
+        { ConsoleColor.DarkRed, Color.DarkRed },
+        { ConsoleColor.DarkMagenta, Color.DarkMagenta },
+        { ConsoleColor.DarkYellow, Color.Yellow },
+        { ConsoleColor.Gray, Color.LightGray },
+        { ConsoleColor.DarkGray, Color.DarkGray },
+        { ConsoleColor.Blue, Color.CornflowerBlue } ,
+        { ConsoleColor.Green, Color.LimeGreen },
+        { ConsoleColor.Cyan, Color.Cyan },
+        { ConsoleColor.Red, Color.IndianRed },
+        { ConsoleColor.Magenta, Color.Magenta },
+        { ConsoleColor.Yellow, Color.Yellow },
+        { ConsoleColor.White, Color.White },
+    };
 
-        internal static Color ConsoleColorToDrawingColor(ConsoleColor color)
-        {
-            if (!ConsoleColorDict.ContainsKey(color))
-                return Color.White;
-
-            return ConsoleColorDict[color];
-        }
+    internal static Dictionary DrawingColorDict = new()
+    {
+        { Color.Black, ConsoleColor.Black },
+        { Color.DarkBlue, ConsoleColor.DarkBlue },
+        { Color.DarkGreen, ConsoleColor.DarkGreen },
+        { Color.DarkCyan, ConsoleColor.DarkCyan },
+        { Color.DarkRed, ConsoleColor.DarkRed },
+        { Color.DarkMagenta, ConsoleColor.DarkMagenta },
+        { Color.Yellow, ConsoleColor.Yellow },
+        { Color.LightGray, ConsoleColor.Gray },
+        { Color.DarkGray, ConsoleColor.DarkGray },
+        { Color.CornflowerBlue, ConsoleColor.Blue } ,
+        { Color.LimeGreen, ConsoleColor.Green },
+        { Color.Cyan, ConsoleColor.Cyan },
+        { Color.IndianRed, ConsoleColor.Red },
+        { Color.Magenta, ConsoleColor.Magenta },
+        { Color.White, ConsoleColor.White },
+    };
 
-        internal static ConsoleColor DrawingColorToConsoleColor(Color color)
-        {
-            if (!DrawingColorDict.ContainsKey(color))
-                return ConsoleColor.White;
+    internal static Color ConsoleColorToDrawingColor(ConsoleColor color)
+    {
+        return !ConsoleColorDict.ContainsKey(color) ? Color.White : ConsoleColorDict[color];
+    }
 
-            return DrawingColorDict[color];
-        }
+    internal static ConsoleColor DrawingColorToConsoleColor(Color color)
+    {
+        return !DrawingColorDict.ContainsKey(color) ? ConsoleColor.White : DrawingColorDict[color];
     }
 }
diff --git a/MelonLoader/Utils/MelonEnvironment.cs b/MelonLoader/Utils/MelonEnvironment.cs
index c10f77e55..af203f282 100644
--- a/MelonLoader/Utils/MelonEnvironment.cs
+++ b/MelonLoader/Utils/MelonEnvironment.cs
@@ -1,57 +1,54 @@
-using System.IO;
-using System.Diagnostics;
-using System;
+using System.Diagnostics;
+using System.IO;
 
-namespace MelonLoader.Utils
+namespace MelonLoader.Utils;
+
+public static class MelonEnvironment
 {
-    public static class MelonEnvironment
-    {
-        private const string OurRuntimeName =
+    private const string OurRuntimeName =
 #if !NET6_0
-            "net35";
+        "net35";
 #else
-            "net6";
+        "net6";
 #endif
 
-        public static bool IsDotnetRuntime { get; } = OurRuntimeName == "net6";
-        public static bool IsMonoRuntime { get; } = !IsDotnetRuntime;
-
-        public static string MelonBaseDirectory => LoaderConfig.Current.Loader.BaseDirectory;
-
-        public static string GameExecutablePath { get; } = Process.GetCurrentProcess().MainModule.FileName;
-        public static string MelonLoaderDirectory { get; } = Path.Combine(MelonBaseDirectory, "MelonLoader");
-        public static string GameRootDirectory { get; } = Path.GetDirectoryName(GameExecutablePath);
-
-
-        public static string DependenciesDirectory { get; } = Path.Combine(MelonLoaderDirectory, "Dependencies");
-        public static string SupportModuleDirectory { get; } = Path.Combine(DependenciesDirectory, "SupportModules");
-        public static string CompatibilityLayerDirectory { get; } = Path.Combine(DependenciesDirectory, "CompatibilityLayers");
-        public static string Il2CppAssemblyGeneratorDirectory { get; } = Path.Combine(DependenciesDirectory, "Il2CppAssemblyGenerator");
-        public static string ModsDirectory { get; } = Path.Combine(MelonBaseDirectory, "Mods");
-        public static string PluginsDirectory { get; } = Path.Combine(MelonBaseDirectory, "Plugins");
-        public static string UserLibsDirectory { get; } = Path.Combine(MelonBaseDirectory, "UserLibs");
-        public static string UserDataDirectory { get; } = Path.Combine(MelonBaseDirectory, "UserData");
-        public static string MelonLoaderLogsDirectory { get; } = Path.Combine(MelonLoaderDirectory, "Logs");
-        public static string OurRuntimeDirectory { get; } = Path.Combine(MelonLoaderDirectory, OurRuntimeName);
-
-        public static string GameExecutableName { get; } = Path.GetFileNameWithoutExtension(GameExecutablePath);
-        public static string UnityGameDataDirectory { get; } = Path.Combine(GameRootDirectory, GameExecutableName + "_Data");
-        public static string UnityGameManagedDirectory { get; } = Path.Combine(UnityGameDataDirectory, "Managed");
-        public static string Il2CppDataDirectory { get; } = Path.Combine(UnityGameDataDirectory, "il2cpp_data");
-        public static string UnityPlayerPath { get; } = Path.Combine(GameRootDirectory, "UnityPlayer.dll");
-
-        public static string MelonManagedDirectory { get; } = Path.Combine(DependenciesDirectory, "Mono");
-        public static string Il2CppAssembliesDirectory { get; } = Path.Combine(MelonLoaderDirectory, "Il2CppAssemblies");
-
-        internal static void PrintEnvironment()
-        {
-            //These must not be changed, lum needs them
-            MelonLogger.MsgDirect($"Core::BasePath = {MelonBaseDirectory}");
-            MelonLogger.MsgDirect($"Game::BasePath = {GameRootDirectory}");
-            MelonLogger.MsgDirect($"Game::DataPath = {UnityGameDataDirectory}");
-            MelonLogger.MsgDirect($"Game::ApplicationPath = {GameExecutablePath}");
-
-            MelonLogger.MsgDirect($"Runtime Type: {OurRuntimeName}");
-        }
+    public static bool IsDotnetRuntime { get; } = OurRuntimeName == "net6";
+    public static bool IsMonoRuntime { get; } = !IsDotnetRuntime;
+
+    public static string MelonBaseDirectory => LoaderConfig.Current.Loader.BaseDirectory;
+
+    public static string GameExecutablePath { get; } = Process.GetCurrentProcess().MainModule.FileName;
+    public static string MelonLoaderDirectory { get; } = Path.Combine(MelonBaseDirectory, "MelonLoader");
+    public static string GameRootDirectory { get; } = Path.GetDirectoryName(GameExecutablePath);
+
+    public static string DependenciesDirectory { get; } = Path.Combine(MelonLoaderDirectory, "Dependencies");
+    public static string SupportModuleDirectory { get; } = Path.Combine(DependenciesDirectory, "SupportModules");
+    public static string CompatibilityLayerDirectory { get; } = Path.Combine(DependenciesDirectory, "CompatibilityLayers");
+    public static string Il2CppAssemblyGeneratorDirectory { get; } = Path.Combine(DependenciesDirectory, "Il2CppAssemblyGenerator");
+    public static string ModsDirectory { get; } = Path.Combine(MelonBaseDirectory, "Mods");
+    public static string PluginsDirectory { get; } = Path.Combine(MelonBaseDirectory, "Plugins");
+    public static string UserLibsDirectory { get; } = Path.Combine(MelonBaseDirectory, "UserLibs");
+    public static string UserDataDirectory { get; } = Path.Combine(MelonBaseDirectory, "UserData");
+    public static string MelonLoaderLogsDirectory { get; } = Path.Combine(MelonLoaderDirectory, "Logs");
+    public static string OurRuntimeDirectory { get; } = Path.Combine(MelonLoaderDirectory, OurRuntimeName);
+
+    public static string GameExecutableName { get; } = Path.GetFileNameWithoutExtension(GameExecutablePath);
+    public static string UnityGameDataDirectory { get; } = Path.Combine(GameRootDirectory, GameExecutableName + "_Data");
+    public static string UnityGameManagedDirectory { get; } = Path.Combine(UnityGameDataDirectory, "Managed");
+    public static string Il2CppDataDirectory { get; } = Path.Combine(UnityGameDataDirectory, "il2cpp_data");
+    public static string UnityPlayerPath { get; } = Path.Combine(GameRootDirectory, "UnityPlayer.dll");
+
+    public static string MelonManagedDirectory { get; } = Path.Combine(DependenciesDirectory, "Mono");
+    public static string Il2CppAssembliesDirectory { get; } = Path.Combine(MelonLoaderDirectory, "Il2CppAssemblies");
+
+    internal static void PrintEnvironment()
+    {
+        //These must not be changed, lum needs them
+        MelonLogger.MsgDirect($"Core::BasePath = {MelonBaseDirectory}");
+        MelonLogger.MsgDirect($"Game::BasePath = {GameRootDirectory}");
+        MelonLogger.MsgDirect($"Game::DataPath = {UnityGameDataDirectory}");
+        MelonLogger.MsgDirect($"Game::ApplicationPath = {GameExecutablePath}");
+
+        MelonLogger.MsgDirect($"Runtime Type: {OurRuntimeName}");
     }
 }
\ No newline at end of file
diff --git a/MelonLoader/Utils/MonoLibrary.cs b/MelonLoader/Utils/MonoLibrary.cs
index c7491c873..087ff9b75 100644
--- a/MelonLoader/Utils/MonoLibrary.cs
+++ b/MelonLoader/Utils/MonoLibrary.cs
@@ -1,50 +1,49 @@
 #if !NET6_0_OR_GREATER
 
+using MelonLoader.InternalUtils;
 using System;
 using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
-using MelonLoader.InternalUtils;
 
-namespace MelonLoader.Utils
+namespace MelonLoader.Utils;
+
+public class MonoLibrary
 {
-    public class MonoLibrary
+    internal static bool Setup()
     {
-        internal static bool Setup()
+        IntPtr NativeMonoPtr = BootstrapInterop.Library.MonoGetRuntimeHandle();
+        if (NativeMonoPtr == IntPtr.Zero)
         {
-            IntPtr NativeMonoPtr = BootstrapInterop.Library.MonoGetRuntimeHandle();
-            if (NativeMonoPtr == IntPtr.Zero)
-            {
-                MelonLogger.ThrowInternalFailure("[MonoLibrary] Failed to get Mono Library Pointer from Internal Call!");
-                return false;
-            }
-
-            try
-            {
-                Instance = NativeMonoPtr.ToNewNativeLibrary().Instance;
-            }
-            catch (Exception ex)
-            {
-                MelonLogger.ThrowInternalFailure($"[MonoLibrary] Failed to load Mono NativeLibrary!\n{ex}");
-                return false;
-            }
-
-            MelonDebug.Msg("[MonoLibrary] Setup Successful!");
-            return true;
+            MelonLogger.ThrowInternalFailure("[MonoLibrary] Failed to get Mono Library Pointer from Internal Call!");
+            return false;
         }
 
-        public static MonoLibrary Instance { get; private set; }
+        try
+        {
+            Instance = NativeMonoPtr.ToNewNativeLibrary().Instance;
+        }
+        catch (Exception ex)
+        {
+            MelonLogger.ThrowInternalFailure($"[MonoLibrary] Failed to load Mono NativeLibrary!\n{ex}");
+            return false;
+        }
 
-        [MethodImpl(MethodImplOptions.InternalCall)]
-        public extern static Assembly CastManagedAssemblyPtr(IntPtr ptr);
+        MelonDebug.Msg("[MonoLibrary] Setup Successful!");
+        return true;
+    }
 
-        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
-        public delegate IntPtr dmono_assembly_open_full(IntPtr filepath, IntPtr status, bool refonly);
-        public dmono_assembly_open_full mono_assembly_open_full = null;
+    public static MonoLibrary Instance { get; private set; }
 
-        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
-        public delegate IntPtr dmono_assembly_get_object(IntPtr domain, IntPtr assembly);
-        public dmono_assembly_get_object mono_assembly_get_object = null;
-    }
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    public static extern Assembly CastManagedAssemblyPtr(IntPtr ptr);
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    public delegate IntPtr dmono_assembly_open_full(IntPtr filepath, IntPtr status, bool refonly);
+    public dmono_assembly_open_full mono_assembly_open_full = null;
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    public delegate IntPtr dmono_assembly_get_object(IntPtr domain, IntPtr assembly);
+    public dmono_assembly_get_object mono_assembly_get_object = null;
 }
 #endif
\ No newline at end of file
diff --git a/MelonLoader/Utils/SteamManifestReader.cs b/MelonLoader/Utils/SteamManifestReader.cs
index d35e0a2ac..3b3a3b68f 100644
--- a/MelonLoader/Utils/SteamManifestReader.cs
+++ b/MelonLoader/Utils/SteamManifestReader.cs
@@ -3,116 +3,111 @@
 using System.Text.RegularExpressions;
 #pragma warning disable CA1416
 
-namespace MelonLoader.Utils
+namespace MelonLoader.Utils;
+
+public static class SteamManifestReader
 {
-    public static class SteamManifestReader
+    public static string GetInstallPathFromAppId(string appid)
     {
-        public static string GetInstallPathFromAppId(string appid)
-        {
-            if (!MelonUtils.IsWindows
-                || MelonUtils.IsUnderWineOrSteamProton())
-                return null;
+        if (!MelonUtils.IsWindows
+            || MelonUtils.IsUnderWineOrSteamProton())
+            return null;
 
-            if (string.IsNullOrEmpty(appid))
-                return null;
+        if (string.IsNullOrEmpty(appid))
+            return null;
 
-            string steaminstallpath = GetSteamInstallPath();
-            if (string.IsNullOrEmpty(steaminstallpath) || !Directory.Exists(steaminstallpath))
-                return null;
+        var steaminstallpath = GetSteamInstallPath();
+        if (string.IsNullOrEmpty(steaminstallpath) || !Directory.Exists(steaminstallpath))
+            return null;
 
-            string steamappspath = Path.Combine(steaminstallpath, "steamapps");
-            if (!Directory.Exists(steamappspath))
-                return null;
+        var steamappspath = Path.Combine(steaminstallpath, "steamapps");
+        if (!Directory.Exists(steamappspath))
+            return null;
 
-            string appmanifestfilename = ("appmanifest_" + appid + ".acf");
-            string appmanifestpath = Path.Combine(steamappspath, appmanifestfilename);
-            string installdir = ReadAppManifestInstallDir(appmanifestpath);
+        var appmanifestfilename = "appmanifest_" + appid + ".acf";
+        var appmanifestpath = Path.Combine(steamappspath, appmanifestfilename);
+        var installdir = ReadAppManifestInstallDir(appmanifestpath);
+        if (string.IsNullOrEmpty(installdir))
+        {
+            installdir = ReadLibraryFolders(appmanifestfilename, ref steamappspath);
             if (string.IsNullOrEmpty(installdir))
-            {
-                installdir = ReadLibraryFolders(appmanifestfilename, ref steamappspath);
-                if (string.IsNullOrEmpty(installdir))
-                    return null;
-            }
-
-            return installdir;
+                return null;
         }
 
-        private static string ReadAppManifestInstallDir(string appmanifestpath)
-        {
-            if (!MelonUtils.IsWindows
-                || MelonUtils.IsUnderWineOrSteamProton())
-                return null;
+        return installdir;
+    }
 
-            if (!File.Exists(appmanifestpath))
-                return null;
+    private static string ReadAppManifestInstallDir(string appmanifestpath)
+    {
+        if (!MelonUtils.IsWindows
+            || MelonUtils.IsUnderWineOrSteamProton())
+            return null;
 
-            string[] file_lines = File.ReadAllLines(appmanifestpath);
-            if (file_lines.Length <= 0)
-                return null;
+        if (!File.Exists(appmanifestpath))
+            return null;
 
-            string output = null;
-            foreach (string line in file_lines)
-            {
-                Match match = new Regex(@"""installdir""\s+""(.+)""").Match(line);
-                if (!match.Success)
-                    continue;
-
-                output = match.Groups[1].Value;
-                break;
-            }
-            return output;
-        }
+        var file_lines = File.ReadAllLines(appmanifestpath);
+        if (file_lines.Length <= 0)
+            return null;
 
-        private static string ReadLibraryFolders(string appmanifestfilename, ref string steamappspath)
+        string output = null;
+        foreach (var line in file_lines)
         {
-            if (!MelonUtils.IsWindows
-                || MelonUtils.IsUnderWineOrSteamProton())
-                return null;
+            var match = new Regex(@"""installdir""\s+""(.+)""").Match(line);
+            if (!match.Success)
+                continue;
 
-            string libraryfoldersfilepath = Path.Combine(steamappspath, "libraryfolders.vdf");
-            if (!File.Exists(libraryfoldersfilepath))
-                return null;
-            string[] file_lines = File.ReadAllLines(libraryfoldersfilepath);
-            if (file_lines.Length <= 0)
-                return null;
-            string output = null;
-            foreach (string line in file_lines)
-            {
-                Match match = new Regex(@"""\d+""\s+""(.+)""").Match(line);
-                if (!match.Success)
-                    continue;
-
-                string steamappspath2 = Path.Combine(match.Groups[1].Value.Replace(":\\\\", ":\\"), "steamapps");
-                if (!Directory.Exists(steamappspath2))
-                    continue;
-
-                string installdir = ReadAppManifestInstallDir(Path.Combine(steamappspath2, appmanifestfilename));
-                if (string.IsNullOrEmpty(installdir))
-                    continue;
-
-                steamappspath = steamappspath2;
-                output = installdir;
-            }
-            return output;
+            output = match.Groups[1].Value;
+            break;
         }
+        return output;
+    }
 
-        private static string GetSteamInstallPath()
+    private static string ReadLibraryFolders(string appmanifestfilename, ref string steamappspath)
+    {
+        if (!MelonUtils.IsWindows
+            || MelonUtils.IsUnderWineOrSteamProton())
+            return null;
+
+        var libraryfoldersfilepath = Path.Combine(steamappspath, "libraryfolders.vdf");
+        if (!File.Exists(libraryfoldersfilepath))
+            return null;
+        var file_lines = File.ReadAllLines(libraryfoldersfilepath);
+        if (file_lines.Length <= 0)
+            return null;
+        string output = null;
+        foreach (var line in file_lines)
         {
-            if (!MelonUtils.IsWindows
-                || MelonUtils.IsUnderWineOrSteamProton())
-                return null;
+            var match = new Regex(@"""\d+""\s+""(.+)""").Match(line);
+            if (!match.Success)
+                continue;
 
-            RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Wow6432Node\\Valve\\Steam");
-            if (key == null)
-                key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Valve\\Steam");
-            if (key == null)
-                return null;
+            var steamappspath2 = Path.Combine(match.Groups[1].Value.Replace(":\\\\", ":\\"), "steamapps");
+            if (!Directory.Exists(steamappspath2))
+                continue;
 
-            object installpathobj = key.GetValue("InstallPath");
-            if (installpathobj == null)
-                return null;
+            var installdir = ReadAppManifestInstallDir(Path.Combine(steamappspath2, appmanifestfilename));
+            if (string.IsNullOrEmpty(installdir))
+                continue;
 
-            return installpathobj.ToString();
+            steamappspath = steamappspath2;
+            output = installdir;
         }
+        return output;
+    }
+
+    private static string GetSteamInstallPath()
+    {
+        if (!MelonUtils.IsWindows
+            || MelonUtils.IsUnderWineOrSteamProton())
+            return null;
+
+        var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Wow6432Node\\Valve\\Steam");
+        key ??= Registry.LocalMachine.OpenSubKey("SOFTWARE\\Valve\\Steam");
+        if (key == null)
+            return null;
+
+        var installpathobj = key.GetValue("InstallPath");
+        return installpathobj == null ? null : installpathobj.ToString();
     }
 }
diff --git a/MelonLoader/VerifyLoaderBuildAttribute.cs b/MelonLoader/VerifyLoaderBuildAttribute.cs
index ecfd58593..492c22fe2 100644
--- a/MelonLoader/VerifyLoaderBuildAttribute.cs
+++ b/MelonLoader/VerifyLoaderBuildAttribute.cs
@@ -1,18 +1,17 @@
 using System;
 
-namespace MelonLoader
+namespace MelonLoader;
+
+[AttributeUsage(AttributeTargets.Assembly)]
+public class VerifyLoaderBuildAttribute : Attribute
 {
-    [AttributeUsage(AttributeTargets.Assembly)]
-    public class VerifyLoaderBuildAttribute : Attribute
-    {
-        /// 
-        /// Build HashCode of MelonLoader.
-        /// 
-        public string HashCode { get; internal set; }
+    /// 
+    /// Build HashCode of MelonLoader.
+    /// 
+    public string HashCode { get; internal set; }
 
-        public VerifyLoaderBuildAttribute(string hashcode) { HashCode = hashcode; }
+    public VerifyLoaderBuildAttribute(string hashcode) { HashCode = hashcode; }
 
-        public bool IsCompatible(string hashCode)
-            => string.IsNullOrEmpty(HashCode) || string.IsNullOrEmpty(hashCode) || HashCode == hashCode;
-    }
+    public bool IsCompatible(string hashCode)
+        => string.IsNullOrEmpty(HashCode) || string.IsNullOrEmpty(hashCode) || HashCode == hashCode;
 }
\ No newline at end of file
diff --git a/MelonLoader/VerifyLoaderVersionAttribute.cs b/MelonLoader/VerifyLoaderVersionAttribute.cs
index 941f841b0..80679d5cf 100644
--- a/MelonLoader/VerifyLoaderVersionAttribute.cs
+++ b/MelonLoader/VerifyLoaderVersionAttribute.cs
@@ -1,5 +1,5 @@
-using System;
-using Semver;
+using Semver;
+using System;
 
 namespace MelonLoader;
 
@@ -46,5 +46,5 @@ public bool IsCompatible(SemVersion version)
         => SemVer == null || version == null || (IsMinimum ? SemVer <= version : SemVer == version);
 
     public bool IsCompatible(string version)
-        => !SemVersion.TryParse(version, out SemVersion ver) || IsCompatible(ver);
+        => !SemVersion.TryParse(version, out var ver) || IsCompatible(ver);
 }
\ No newline at end of file

From af25a4ab743978a2d1660a7fde8994e2024ec0ce Mon Sep 17 00:00:00 2001
From: slxdy 
Date: Wed, 22 Jan 2025 20:39:50 +0100
Subject: [PATCH 09/18] Cleanup MelonLoader (2nd iter.)

---
 MelonLoader/Assertions/LemonAssert.cs         |  4 +-
 MelonLoader/Assertions/LemonAssertMapping.cs  |  4 +-
 .../Melon/MelonPrefs.cs                       |  4 +-
 MelonLoader/Fixes/DetourContextDisposeFix.cs  |  1 -
 MelonLoader/Fixes/ForcedCultureInfo.cs        |  3 +-
 MelonLoader/Fixes/InstancePatchFix.cs         |  6 +--
 .../SharpZipLib/BZip2/BZip2OutputStream.cs    |  9 +---
 .../SharpZipLib/Core/NameFilter.cs            | 22 +---------
 .../SharpZipLib/GZip/GzipInputStream.cs       |  8 ++--
 .../ICSharpCode/SharpZipLib/Tar/TarArchive.cs | 22 +++-------
 .../ICSharpCode/SharpZipLib/Tar/TarHeader.cs  | 20 +++------
 .../SharpZipLib/Zip/Compression/Inflater.cs   |  9 +---
 .../ICSharpCode/SharpZipLib/Zip/FastZip.cs    |  2 +-
 .../ICSharpCode/SharpZipLib/Zip/ZipEntry.cs   |  9 ++--
 .../SharpZipLib/Zip/ZipEntryFactory.cs        |  2 +-
 .../SharpZipLib/Zip/ZipExtraData.cs           |  2 +-
 .../ICSharpCode/SharpZipLib/Zip/ZipFile.cs    | 43 +++++++++----------
 .../SharpZipLib/Zip/ZipInputStream.cs         | 22 +++-------
 .../InternalUtils/UnityInformationHandler.cs  |  4 +-
 MelonLoader/InteropSupport.cs                 |  6 +--
 MelonLoader/LemonArraySegment.cs              | 12 +++---
 MelonLoader/MelonBase.cs                      |  2 +-
 MelonLoader/MelonPreferences.cs               |  8 ++--
 MelonLoader/MelonPreferences_Category.cs      |  6 +--
 MelonLoader/MelonUtils.cs                     |  8 ++--
 MelonLoader/Modules/MelonModule.cs            |  2 +-
 MelonLoader/NativeLibrary.cs                  |  4 ++
 MelonLoader/Preferences/IO/Watcher.cs         |  1 -
 MelonLoader/Preferences/ValueValidator.cs     |  4 +-
 MelonLoader/Resolver/AssemblyResolveInfo.cs   |  2 +-
 MelonLoader/Semver/IntExtensions.cs           |  4 +-
 MelonLoader/Semver/SemVersion.cs              |  8 +---
 MelonLoader/Utils/SteamManifestReader.cs      |  2 +-
 33 files changed, 95 insertions(+), 170 deletions(-)

diff --git a/MelonLoader/Assertions/LemonAssert.cs b/MelonLoader/Assertions/LemonAssert.cs
index b03f801a2..f0868dcc5 100644
--- a/MelonLoader/Assertions/LemonAssert.cs
+++ b/MelonLoader/Assertions/LemonAssert.cs
@@ -1,6 +1,4 @@
-using System;
-
-namespace MelonLoader.Assertions;
+namespace MelonLoader.Assertions;
 
 public static class LemonAssert
 {
diff --git a/MelonLoader/Assertions/LemonAssertMapping.cs b/MelonLoader/Assertions/LemonAssertMapping.cs
index f222f2d5f..c98b19336 100644
--- a/MelonLoader/Assertions/LemonAssertMapping.cs
+++ b/MelonLoader/Assertions/LemonAssertMapping.cs
@@ -36,8 +36,6 @@ private static bool IsNull_string(string obj)
         => string.IsNullOrEmpty(obj);
     private static bool IsEqual_object(object obj, object obj2)
     {
-        if (obj == null)
-            return obj2 == null;
-        return obj2 == null ? obj == null : obj.Equals(obj2);
+        return obj == null ? obj2 == null : obj2 == null ? obj == null : obj.Equals(obj2);
     }
 }
diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs
index d40c21131..dca2c9007 100644
--- a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs
+++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs
@@ -55,7 +55,7 @@ public static string GetString(string section, string name)
         if (category == null)
             return null;
         var entry = category.GetEntry(name);
-        return entry == null ? null : entry.GetValueAsString();
+        return entry?.GetValueAsString();
     }
     [Obsolete("MelonLoader.MelonPrefs.SetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryString instead.")]
     public static void SetString(string section, string name, string value)
@@ -145,7 +145,7 @@ private static string GetEditedString(string section, string name)
             if (category == null)
                 return null;
             var entry = category.GetEntry(name);
-            return entry == null ? null : entry.GetEditedValueAsString();
+            return entry?.GetEditedValueAsString();
         }
         private static void SetEditedString(string section, string name, string value)
         {
diff --git a/MelonLoader/Fixes/DetourContextDisposeFix.cs b/MelonLoader/Fixes/DetourContextDisposeFix.cs
index 2442d264d..f1acc600d 100644
--- a/MelonLoader/Fixes/DetourContextDisposeFix.cs
+++ b/MelonLoader/Fixes/DetourContextDisposeFix.cs
@@ -1,6 +1,5 @@
 using HarmonyLib;
 using MonoMod.RuntimeDetour;
-using System;
 using System.Collections.Generic;
 using System.Reflection;
 using System.Reflection.Emit;
diff --git a/MelonLoader/Fixes/ForcedCultureInfo.cs b/MelonLoader/Fixes/ForcedCultureInfo.cs
index add7eb09b..cd2423c6b 100644
--- a/MelonLoader/Fixes/ForcedCultureInfo.cs
+++ b/MelonLoader/Fixes/ForcedCultureInfo.cs
@@ -1,5 +1,4 @@
-using HarmonyLib;
-using System;
+using System;
 using System.Globalization;
 using System.Linq;
 using System.Reflection;
diff --git a/MelonLoader/Fixes/InstancePatchFix.cs b/MelonLoader/Fixes/InstancePatchFix.cs
index d7a8e8adc..be2a87068 100644
--- a/MelonLoader/Fixes/InstancePatchFix.cs
+++ b/MelonLoader/Fixes/InstancePatchFix.cs
@@ -28,8 +28,8 @@ internal static void Install()
 
     private static bool PatchMethod(MethodBase __0)
     {
-        if (__0 == null)
-            throw new NullReferenceException("Patch Method");
-        return (__0 != null) && !__0.IsStatic ? throw new Exception("Patch Method must be a Static Method!") : true;
+        return __0 == null
+            ? throw new NullReferenceException("Patch Method")
+            : (__0 != null) && !__0.IsStatic ? throw new Exception("Patch Method must be a Static Method!") : true;
     }
 }
\ No newline at end of file
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
index 6921f9927..4f5abfd8c 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
@@ -659,13 +659,9 @@ private void SendMTFValues()
         {
             nGroups = 3;
         }
-        else if (nMTF < 1200)
-        {
-            nGroups = 4;
-        }
         else
         {
-            nGroups = nMTF < 2400 ? 5 : 6;
+            nGroups = nMTF < 1200 ? 4 : nMTF < 2400 ? 5 : 6;
         }
 
         /*--- Generate an initial set of coding tables ---*/
@@ -1919,7 +1915,7 @@ Nodes and heap entries run from 1.  Entry 0
                 parent[n1] = parent[n2] = nNodes;
 
                 weight[nNodes] = (int)((weight[n1] & 0xffffff00) + (weight[n2] & 0xffffff00)) |
-                    1 + (((weight[n1] & 0x000000ff) > (weight[n2] & 0x000000ff)) ? (weight[n1] & 0x000000ff) : (weight[n2] & 0x000000ff));
+                    (1 + (((weight[n1] & 0x000000ff) > (weight[n2] & 0x000000ff)) ? (weight[n1] & 0x000000ff) : (weight[n2] & 0x000000ff)));
 
                 parent[nNodes] = -1;
                 nHeap++;
@@ -1995,7 +1991,6 @@ private static byte Med3(byte a, byte b, byte c)
         }
         if (b > c)
         {
-            t = b;
             b = c;
         }
         if (a > b)
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
index 939f76769..e37caefa7 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
@@ -74,16 +74,7 @@ public static bool IsValidFilterExpression(string toTest)
                 {
                     if ((items[i] != null) && (items[i].Length > 0))
                     {
-                        string toCompile;
-
-                        if (items[i][0] == '+')
-                        {
-                            toCompile = items[i][1..];
-                        }
-                        else
-                        {
-                            toCompile = items[i][0] == '-' ? items[i][1..] : items[i];
-                        }
+                        var toCompile = items[i][0] == '+' ? items[i][1..] : items[i][0] == '-' ? items[i][1..] : items[i];
 
                         var testRegex = new Regex(toCompile, RegexOptions.IgnoreCase | RegexOptions.Singleline);
                     }
@@ -239,16 +230,7 @@ private void Compile()
             if ((items[i] != null) && (items[i].Length > 0))
             {
                 var include = items[i][0] != '-';
-                string toCompile;
-
-                if (items[i][0] == '+')
-                {
-                    toCompile = items[i][1..];
-                }
-                else
-                {
-                    toCompile = items[i][0] == '-' ? items[i][1..] : items[i];
-                }
+                var toCompile = items[i][0] == '+' ? items[i][1..] : items[i][0] == '-' ? items[i][1..] : items[i];
 
                 // NOTE: Regular expressions can fail to compile here for a number of reasons that cause an exception
                 // these are left unhandled here as the caller is responsible for ensuring all is valid.
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
index 50232fd45..32938ef74 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
@@ -338,10 +338,10 @@ private void ReadFooter()
 
         // NOTE The total here is the original total modulo 2 ^ 32.
         var total =
-            (uint)footer[4] & 0xff |
-            ((uint)footer[5] & 0xff) << 8 |
-            ((uint)footer[6] & 0xff) << 16 |
-            (uint)footer[7] << 24;
+            ((uint)footer[4] & 0xff) |
+            (((uint)footer[5] & 0xff) << 8) |
+            (((uint)footer[6] & 0xff) << 16) |
+            ((uint)footer[7] << 24);
 
         if (bytesRead != total)
         {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
index 696b9d9ae..9e2f5a1cc 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
@@ -113,7 +113,6 @@ public static TarArchive CreateInputTarArchive(Stream inputStream, Encoding name
             throw new ArgumentNullException(nameof(inputStream));
         }
 
-
         var result = inputStream is TarInputStream tarStream ? new TarArchive(tarStream) : CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding);
         return result;
     }
@@ -139,12 +138,9 @@ public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFact
     /// Returns a  suitable for reading.
     public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor, Encoding nameEncoding)
     {
-        if (inputStream == null)
-        {
-            throw new ArgumentNullException(nameof(inputStream));
-        }
-
-        return inputStream is TarInputStream
+        return inputStream == null
+            ? throw new ArgumentNullException(nameof(inputStream))
+            : inputStream is TarInputStream
             ? throw new ArgumentException("TarInputStream not valid")
             : new TarArchive(new TarInputStream(inputStream, blockFactor, nameEncoding));
     }
@@ -161,7 +157,6 @@ public static TarArchive CreateOutputTarArchive(Stream outputStream, Encoding na
             throw new ArgumentNullException(nameof(outputStream));
         }
 
-
         var result = outputStream is TarOutputStream tarStream
             ? new TarArchive(tarStream)
             : CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding);
@@ -196,12 +191,9 @@ public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFa
     /// Returns a  suitable for writing.
     public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor, Encoding nameEncoding)
     {
-        if (outputStream == null)
-        {
-            throw new ArgumentNullException(nameof(outputStream));
-        }
-
-        return outputStream is TarOutputStream
+        return outputStream == null
+            ? throw new ArgumentNullException(nameof(outputStream))
+            : outputStream is TarOutputStream
             ? throw new ArgumentException("TarOutputStream is not valid")
             : new TarArchive(new TarOutputStream(outputStream, blockFactor, nameEncoding));
     }
@@ -912,7 +904,7 @@ private static bool IsBinary(string filename)
         for (var i = 0; i < bytesRead; ++i)
         {
             var b = content[i];
-            if (b is < 8 or > 13 and < 32 or 255)
+            if (b is < 8 or (> 13 and < 32) or 255)
             {
                 return true;
             }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs
index eb2f7fa94..fc8e480f0 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs
@@ -462,7 +462,7 @@ public string GroupName
         get { return groupName; }
         set
         {
-            groupName = value == null ? "None" : value;
+            groupName = value ?? "None";
         }
     }
 
@@ -978,12 +978,9 @@ public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, in
     /// 
     public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length, Encoding encoding)
     {
-        if (name == null)
-        {
-            throw new ArgumentNullException(nameof(name));
-        }
-
-        return buffer == null
+        return name == null
+            ? throw new ArgumentNullException(nameof(name))
+            : buffer == null
             ? throw new ArgumentNullException(nameof(buffer))
             : GetNameBytes(name.ToString(), 0, buffer, offset, length, encoding);
     }
@@ -1014,12 +1011,9 @@ public static int GetNameBytes(string name, byte[] buffer, int offset, int lengt
     /// The index of the next free byte in the buffer
     public static int GetNameBytes(string name, byte[] buffer, int offset, int length, Encoding encoding)
     {
-        if (name == null)
-        {
-            throw new ArgumentNullException(nameof(name));
-        }
-
-        return buffer == null ? throw new ArgumentNullException(nameof(buffer)) : GetNameBytes(name, 0, buffer, offset, length, encoding);
+        return name == null
+            ? throw new ArgumentNullException(nameof(name))
+            : buffer == null ? throw new ArgumentNullException(nameof(buffer)) : GetNameBytes(name, 0, buffer, offset, length, encoding);
     }
     /// 
     /// Add a string to a buffer as a collection of ascii bytes.
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs
index 9f6f2efa6..69585d777 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs
@@ -819,14 +819,7 @@ public int Adler
     {
         get
         {
-            if (IsNeedingDictionary)
-            {
-                return readAdler;
-            }
-            else
-            {
-                return adler != null ? (int)adler.Value : 0;
-            }
+            return IsNeedingDictionary ? readAdler : adler != null ? (int)adler.Value : 0;
         }
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs
index c1bf60c31..89c8d0196 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs
@@ -280,7 +280,7 @@ public IEntryFactory EntryFactory
         get { return entryFactory_; }
         set
         {
-            entryFactory_ = value == null ? new ZipEntryFactory() : value;
+            entryFactory_ = value ?? new ZipEntryFactory();
         }
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
index b93882bd5..bd375ae32 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
@@ -498,10 +498,7 @@ public int Version
             if (CentralHeaderRequiresZip64)
                 return ZipConstants.VersionZip64;
 
-            if (CompressionMethod.Deflated == method || IsDirectory || IsCrypted)
-                return 20;
-
-            return HasDosAttributes(0x08) ? 11 : 10;
+            return CompressionMethod.Deflated == method || IsDirectory || IsCrypted ? 20 : HasDosAttributes(0x08) ? 11 : 10;
         }
     }
 
@@ -1006,8 +1003,8 @@ public string Comment
     /// The trailing slash convention should always be followed.
     /// 
     public bool IsDirectory
-        => name.Length > 0
-        && (name[^1] == '/' || name[^1] == '\\') || HasDosAttributes(16);
+        => (name.Length > 0
+        && (name[^1] == '/' || name[^1] == '\\')) || HasDosAttributes(16);
 
     /// 
     /// Get a value of true if the entry appears to be a file; false otherwise
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs
index c34a38056..40c03972e 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs
@@ -105,7 +105,7 @@ public INameTransform NameTransform
         get { return nameTransform_; }
         set
         {
-            nameTransform_ = value == null ? new ZipNameTransform() : value;
+            nameTransform_ = value ?? new ZipNameTransform();
         }
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs
index f885e8d54..9388b03c9 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs
@@ -513,7 +513,7 @@ public ZipExtraData()
     /// The extra data.
     public ZipExtraData(byte[] data)
     {
-        _data = data == null ? Empty.Array() : data;
+        _data = data ?? Empty.Array();
     }
 
     #endregion Constructors
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs
index b10610cfa..abd30091b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs
@@ -1144,7 +1144,7 @@ private long TestLocalHeader(ZipEntry entry, HeaderTest tests)
                         throw new ZipException("Compression method not supported");
                     }
 
-                    if (extractVersion is > ZipConstants.VersionMadeBy or > 20 and < ZipConstants.VersionZip64)
+                    if (extractVersion is > ZipConstants.VersionMadeBy or (> 20 and < ZipConstants.VersionZip64))
                     {
                         throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion));
                     }
@@ -1385,7 +1385,7 @@ public IEntryFactory EntryFactory
 
         set
         {
-            updateEntryFactory_ = value == null ? new ZipEntryFactory() : value;
+            updateEntryFactory_ = value ?? new ZipEntryFactory();
         }
     }
 
@@ -2931,14 +2931,7 @@ public int Compare(ZipUpdate x, ZipUpdate y)
                 if (result == 0)
                 {
                     var offsetDiff = x.Entry.Offset - y.Entry.Offset;
-                    if (offsetDiff < 0)
-                    {
-                        result = -1;
-                    }
-                    else
-                    {
-                        result = offsetDiff == 0 ? 0 : 1;
-                    }
+                    result = offsetDiff < 0 ? -1 : offsetDiff == 0 ? 0 : 1;
                 }
             }
             return result;
@@ -3475,7 +3468,8 @@ private void ReadEntries()
             // total number of disks 4 bytes
             ReadLEUint(); // startDisk64 is not currently used
             var offset64 = ReadLEUlong();
-            var totalDisks = ReadLEUint();
+
+            _ = ReadLEUint();
 
             baseStream_.Position = (long)offset64;
             long sig64 = ReadLEUint();
@@ -3486,13 +3480,17 @@ private void ReadEntries()
             }
 
             // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
-            var recordSize = ReadLEUlong();
-            int versionMadeBy = ReadLEUshort();
-            int versionToExtract = ReadLEUshort();
-            var thisDisk = ReadLEUint();
-            var centralDirDisk = ReadLEUint();
+            _ = ReadLEUlong();
+
+            _ = ReadLEUshort();
+
+            _ = ReadLEUshort();
+
+            _ = ReadLEUint();
+
+            _ = ReadLEUint();
             entriesForThisDisk = ReadLEUlong();
-            entriesForWholeCentralDir = ReadLEUlong();
+            _ = ReadLEUlong();
             centralDirSize = ReadLEUlong();
             offsetOfCentralDir = (long)ReadLEUlong();
 
@@ -3537,8 +3535,9 @@ private void ReadEntries()
             int extraLen = ReadLEUshort();
             int commentLen = ReadLEUshort();
 
-            int diskStartNo = ReadLEUshort();  // Not currently used
-            int internalAttributes = ReadLEUshort();  // Not currently used
+            _ = ReadLEUshort();  // Not currently used
+
+            _ = ReadLEUshort();  // Not currently used
 
             var externalAttributes = ReadLEUint();
             long offset = ReadLEUint();
@@ -3557,11 +3556,10 @@ private void ReadEntries()
                 DosTime = dostime,
                 ZipFileIndex = (long)i,
                 Offset = offset,
-                ExternalFileAttributes = (int)externalAttributes
+                ExternalFileAttributes = (int)externalAttributes,
+                CryptoCheckValue = (bitFlags & 8) == 0 ? (byte)(crc >> 24) : (byte)((dostime >> 8) & 0xff)
             };
 
-            entry.CryptoCheckValue = (bitFlags & 8) == 0 ? (byte)(crc >> 24) : (byte)((dostime >> 8) & 0xff);
-
             if (extraLen > 0)
             {
                 var extra = new byte[extraLen];
@@ -4611,7 +4609,6 @@ public override Stream ConvertTemporaryToFinal()
         var moveTempName = PathUtils.GetTempFileName(fileName_);
         var newFileCreated = false;
 
-
         Stream result;
         try
         {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs
index 74a94fe8a..e99c46e59 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs
@@ -433,14 +433,9 @@ public override long Length
     {
         get
         {
-            if (entry != null)
-            {
-                return entry.Size >= 0 ? entry.Size : throw new ZipException("Length not available for the current entry");
-            }
-            else
-            {
-                throw new InvalidOperationException("No current entry");
-            }
+            return entry != null
+                ? entry.Size >= 0 ? entry.Size : throw new ZipException("Length not available for the current entry")
+                : throw new InvalidOperationException("No current entry");
         }
     }
 
@@ -483,7 +478,6 @@ private int StoredDescriptorEntry(byte[] destination, int offset, int count) =>
         throw new StreamUnsupportedException(
             "The combination of Stored compression method and Descriptor flag is not possible to read using ZipInputStream");
 
-
     /// 
     /// Perform the initial read on an entry which may include
     /// reading encryption headers and setting up inflation.
@@ -556,7 +550,6 @@ private int InitialRead(byte[] destination, int offset, int count)
             return BodyRead(destination, offset, count);
         }
 
-
         internalReader = ReadingNotAvailable;
         return 0;
     }
@@ -581,12 +574,9 @@ public override int Read(byte[] buffer, int offset, int count)
             throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
         }
 
-        if (count < 0)
-        {
-            throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
-        }
-
-        return (buffer.Length - offset) < count
+        return count < 0
+            ? throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative")
+            : (buffer.Length - offset) < count
             ? throw new ArgumentException("Invalid offset/count combination")
             : internalReader(buffer, offset, count);
     }
diff --git a/MelonLoader/InternalUtils/UnityInformationHandler.cs b/MelonLoader/InternalUtils/UnityInformationHandler.cs
index b4a4c9abf..bb2175f44 100644
--- a/MelonLoader/InternalUtils/UnityInformationHandler.cs
+++ b/MelonLoader/InternalUtils/UnityInformationHandler.cs
@@ -1,8 +1,6 @@
-using AssetsTools.NET;
-using AssetsTools.NET.Extra;
+using AssetsTools.NET.Extra;
 using MelonLoader.Utils;
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Drawing;
 using System.IO;
diff --git a/MelonLoader/InteropSupport.cs b/MelonLoader/InteropSupport.cs
index 5361d9fbd..80bea0b40 100644
--- a/MelonLoader/InteropSupport.cs
+++ b/MelonLoader/InteropSupport.cs
@@ -69,9 +69,9 @@ public static FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method)
     public static T Il2CppObjectPtrToIl2CppObject(IntPtr ptr)
     {
         ValidateInterface();
-        if (ptr == IntPtr.Zero)
-            throw new NullReferenceException("The ptr cannot be IntPtr.Zero.");
-        return !IsGeneratedAssemblyType(typeof(T))
+        return ptr == IntPtr.Zero
+            ? throw new NullReferenceException("The ptr cannot be IntPtr.Zero.")
+            : !IsGeneratedAssemblyType(typeof(T))
             ? throw new NullReferenceException("The type must be a Generated Assembly Type.")
             : (T)typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(IntPtr) }, new ParameterModifier[0]).Invoke(new object[] { ptr });
     }
diff --git a/MelonLoader/LemonArraySegment.cs b/MelonLoader/LemonArraySegment.cs
index 62bfcb598..33656de08 100644
--- a/MelonLoader/LemonArraySegment.cs
+++ b/MelonLoader/LemonArraySegment.cs
@@ -101,9 +101,9 @@ T IList.this[int index]
     {
         get
         {
-            if (Array == null)
-                throw new InvalidOperationException("The underlying array is null.");
-            return index < 0 || index >= Count ? throw new ArgumentOutOfRangeException("index") : Array[Offset + index];
+            return Array == null
+                ? throw new InvalidOperationException("The underlying array is null.")
+                : index < 0 || index >= Count ? throw new ArgumentOutOfRangeException("index") : Array[Offset + index];
         }
         set
         {
@@ -196,9 +196,9 @@ public T Current
         {
             get
             {
-                if (_current < _start)
-                    throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
-                return _current >= _end ? throw new InvalidOperationException("Enumeration already finished.") : _array[_current];
+                return _current < _start
+                    ? throw new InvalidOperationException("Enumeration has not started. Call MoveNext.")
+                    : _current >= _end ? throw new InvalidOperationException("Enumeration already finished.") : _array[_current];
             }
         }
 
diff --git a/MelonLoader/MelonBase.cs b/MelonLoader/MelonBase.cs
index 0178b90dd..629e30a7a 100644
--- a/MelonLoader/MelonBase.cs
+++ b/MelonLoader/MelonBase.cs
@@ -618,7 +618,7 @@ public object SendMessage(string name, params object[] arguments)
     {
         var msg = Info.SystemType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
 
-        return msg == null ? null : msg.Invoke(msg.IsStatic ? null : this, arguments);
+        return msg?.Invoke(msg.IsStatic ? null : this, arguments);
     }
     #endregion
 
diff --git a/MelonLoader/MelonPreferences.cs b/MelonLoader/MelonPreferences.cs
index 1d784073c..802128192 100644
--- a/MelonLoader/MelonPreferences.cs
+++ b/MelonLoader/MelonPreferences.cs
@@ -176,7 +176,7 @@ public static MelonPreferences_Category CreateCategory(string identifier, string
             throw new Exception("identifier is null or empty when calling CreateCategory");
         display_name ??= identifier;
         var category = GetCategory(identifier);
-        return category != null ? category : new MelonPreferences_Category(identifier, display_name, is_hidden);
+        return category ?? new MelonPreferences_Category(identifier, display_name, is_hidden);
     }
 
     public static MelonPreferences_ReflectiveCategory CreateCategory(string identifier, string display_name = null) where T : new() => MelonPreferences_ReflectiveCategory.Create(identifier, display_name);
@@ -204,9 +204,9 @@ public static MelonPreferences_Entry CreateEntry(string category_identifie
 
     public static MelonPreferences_Category GetCategory(string identifier)
     {
-        if (string.IsNullOrEmpty(identifier))
-            throw new Exception("identifier is null or empty when calling GetCategory");
-        return Categories.Count <= 0 ? null : Categories.Find(x => x.Identifier.Equals(identifier));
+        return string.IsNullOrEmpty(identifier)
+            ? throw new Exception("identifier is null or empty when calling GetCategory")
+            : Categories.Count <= 0 ? null : Categories.Find(x => x.Identifier.Equals(identifier));
     }
 
     public static T GetCategory(string identifier) where T : new()
diff --git a/MelonLoader/MelonPreferences_Category.cs b/MelonLoader/MelonPreferences_Category.cs
index 6a29488f3..314d2eec8 100644
--- a/MelonLoader/MelonPreferences_Category.cs
+++ b/MelonLoader/MelonPreferences_Category.cs
@@ -98,9 +98,9 @@ public bool RenameEntry(string identifier, string newIdentifier)
 
     public MelonPreferences_Entry GetEntry(string identifier)
     {
-        if (string.IsNullOrEmpty(identifier))
-            throw new Exception("identifier cannot be null or empty when calling GetEntry");
-        return Entries.Count <= 0 ? null : Entries.Find(x => x.Identifier.Equals(identifier));
+        return string.IsNullOrEmpty(identifier)
+            ? throw new Exception("identifier cannot be null or empty when calling GetEntry")
+            : Entries.Count <= 0 ? null : Entries.Find(x => x.Identifier.Equals(identifier));
     }
     public MelonPreferences_Entry GetEntry(string identifier) => (MelonPreferences_Entry)GetEntry(identifier);
     public bool HasEntry(string identifier) => GetEntry(identifier) != null;
diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs
index fc4fe50ee..6794e3a8d 100644
--- a/MelonLoader/MelonUtils.cs
+++ b/MelonLoader/MelonUtils.cs
@@ -70,9 +70,7 @@ internal static void Setup(AppDomain domain)
     public static MelonGameAttribute CurrentGameAttribute { get; private set; }
     public static T Clamp(T value, T min, T max) where T : IComparable
     {
-        if (value.CompareTo(min) < 0)
-            return min;
-        return value.CompareTo(max) > 0 ? max : value;
+        return value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value;
     }
     public static string HashCode { get; private set; }
 
@@ -435,7 +433,7 @@ public static Delegate GetDelegate(this IntPtr ptr, Type type)
         if (ptr == IntPtr.Zero)
             throw new ArgumentNullException(nameof(ptr));
         var del = Marshal.GetDelegateForFunctionPointer(ptr, type);
-        return del == null ? throw new Exception($"Unable to Get Delegate of Type {type.FullName} for Function Pointer!") : del;
+        return del ?? throw new Exception($"Unable to Get Delegate of Type {type.FullName} for Function Pointer!");
     }
     public static IntPtr GetFunctionPointer(this Delegate del)
         => Marshal.GetFunctionPointerForDelegate(del);
@@ -498,7 +496,7 @@ public static void SetConsoleTitle(string title)
     public static string GetFileProductName(string filepath)
     {
         var fileInfo = FileVersionInfo.GetVersionInfo(filepath);
-        return fileInfo != null ? fileInfo.ProductName : null;
+        return fileInfo?.ProductName;
     }
 
     public static void AddNativeDLLDirectory(string path)
diff --git a/MelonLoader/Modules/MelonModule.cs b/MelonLoader/Modules/MelonModule.cs
index c4f016828..e896e7c20 100644
--- a/MelonLoader/Modules/MelonModule.cs
+++ b/MelonLoader/Modules/MelonModule.cs
@@ -112,7 +112,7 @@ public object SendMessage(string name, params object[] arguments)
     {
         var msg = moduleType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
 
-        return msg == null ? null : msg.Invoke(msg.IsStatic ? null : this, arguments);
+        return msg?.Invoke(msg.IsStatic ? null : this, arguments);
     }
 
     public class Info
diff --git a/MelonLoader/NativeLibrary.cs b/MelonLoader/NativeLibrary.cs
index 96bb69418..8c5067172 100644
--- a/MelonLoader/NativeLibrary.cs
+++ b/MelonLoader/NativeLibrary.cs
@@ -3,6 +3,10 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
+#if LINUX
+using System.IO;
+#endif
+
 namespace MelonLoader;
 
 public class NativeLibrary
diff --git a/MelonLoader/Preferences/IO/Watcher.cs b/MelonLoader/Preferences/IO/Watcher.cs
index 46aa6535f..2471572f8 100644
--- a/MelonLoader/Preferences/IO/Watcher.cs
+++ b/MelonLoader/Preferences/IO/Watcher.cs
@@ -1,7 +1,6 @@
 using HarmonyLib;
 using System;
 using System.IO;
-using System.Reflection;
 
 namespace MelonLoader.Preferences.IO;
 
diff --git a/MelonLoader/Preferences/ValueValidator.cs b/MelonLoader/Preferences/ValueValidator.cs
index ec591c482..abedbad85 100644
--- a/MelonLoader/Preferences/ValueValidator.cs
+++ b/MelonLoader/Preferences/ValueValidator.cs
@@ -33,9 +33,7 @@ public override bool IsValid(object value)
 
     public override object EnsureValid(object value)
     {
-        if (MaxValue.CompareTo(value) < 0)
-            return MaxValue;
-        return MinValue.CompareTo(value) > 0 ? MinValue : value;
+        return MaxValue.CompareTo(value) < 0 ? MaxValue : MinValue.CompareTo(value) > 0 ? MinValue : value;
     }
 
     object IValueRange.MinValue => MinValue;
diff --git a/MelonLoader/Resolver/AssemblyResolveInfo.cs b/MelonLoader/Resolver/AssemblyResolveInfo.cs
index a2cf511f8..a18331c25 100644
--- a/MelonLoader/Resolver/AssemblyResolveInfo.cs
+++ b/MelonLoader/Resolver/AssemblyResolveInfo.cs
@@ -22,7 +22,7 @@ internal Assembly Resolve(Version requested_version)
             return assembly;
 
         // Check for Fallback
-        return Fallback != null ? Fallback : null;
+        return Fallback ?? null;
     }
 
     public void SetVersionSpecific(Version version, Assembly assembly = null)
diff --git a/MelonLoader/Semver/IntExtensions.cs b/MelonLoader/Semver/IntExtensions.cs
index 9fe922297..071a9131e 100644
--- a/MelonLoader/Semver/IntExtensions.cs
+++ b/MelonLoader/Semver/IntExtensions.cs
@@ -34,8 +34,6 @@ public static int Digits(this int n)
             return 6;
         if (n < 10_000_000)
             return 7;
-        if (n < 100_000_000)
-            return 8;
-        return n < 1_000_000_000 ? 9 : 10;
+        return n < 100_000_000 ? 8 : n < 1_000_000_000 ? 9 : 10;
     }
 }
diff --git a/MelonLoader/Semver/SemVersion.cs b/MelonLoader/Semver/SemVersion.cs
index e3311e176..3523a5d5b 100644
--- a/MelonLoader/Semver/SemVersion.cs
+++ b/MelonLoader/Semver/SemVersion.cs
@@ -189,9 +189,7 @@ public static bool TryParse(string version, out SemVersion semver, bool strict =
     ///  if the two values are equal, otherwise .
     public static bool Equals(SemVersion versionA, SemVersion versionB)
     {
-        if (ReferenceEquals(versionA, versionB))
-            return true;
-        return versionA is not null && versionB is not null && versionA.Equals(versionB);
+        return ReferenceEquals(versionA, versionB) || versionA is not null && versionB is not null && versionA.Equals(versionB);
     }
 
     /// 
@@ -204,9 +202,7 @@ public static int Compare(SemVersion versionA, SemVersion versionB)
     {
         if (ReferenceEquals(versionA, versionB))
             return 0;
-        if (versionA is null)
-            return -1;
-        return versionB is null ? 1 : versionA.CompareTo(versionB);
+        return versionA is null ? -1 : versionB is null ? 1 : versionA.CompareTo(versionB);
     }
 
     /// 
diff --git a/MelonLoader/Utils/SteamManifestReader.cs b/MelonLoader/Utils/SteamManifestReader.cs
index 3b3a3b68f..200613a93 100644
--- a/MelonLoader/Utils/SteamManifestReader.cs
+++ b/MelonLoader/Utils/SteamManifestReader.cs
@@ -108,6 +108,6 @@ private static string GetSteamInstallPath()
             return null;
 
         var installpathobj = key.GetValue("InstallPath");
-        return installpathobj == null ? null : installpathobj.ToString();
+        return installpathobj?.ToString();
     }
 }

From be9961d3842b5bb30e8d77e5d7eb86b69e62719a Mon Sep 17 00:00:00 2001
From: slxdy 
Date: Wed, 22 Jan 2025 20:55:41 +0100
Subject: [PATCH 10/18] Fix warnings

---
 .../CompatibilityLayers/IPA/Module.cs         |    1 +
 .../Muse_Dash_Mono/Module.cs                  |    1 +
 .../MuseDashModLoader/ModLoader.cs            |    1 +
 Dependencies/Il2CppAssemblyGenerator/Core.cs  |    3 +
 .../Packages/DeobfuscationMap.cs              |    1 +
 .../Packages/DeobfuscationRegex.cs            |    1 +
 .../Il2Cpp/MonoEnumeratorWrapper.cs           |    4 +-
 Dependencies/SupportModules/SceneHandler.cs   |    1 +
 .../Harmony/Extras/FastAccess.cs              |    1 +
 .../Melon/MelonPrefs.cs                       |    2 +
 .../BackwardsCompatibility/Melon/ModPrefs.cs  |    2 +
 .../CoreClrUtils/CoreClrDelegateFixer.cs      |  187 +--
 MelonLoader/CoreClrUtils/MethodBaseHelper.cs  |   59 +-
 MelonLoader/CoreClrUtils/NativeStackWalk.cs   |   76 +-
 MelonLoader/Fixes/AsmResolverFix.cs           |   43 +-
 .../Fixes/DotnetAssemblyLoadContextFix.cs     |  158 +--
 .../Fixes/DotnetModHandlerRedirectionFix.cs   |   36 +-
 MelonLoader/Fixes/Il2CppICallInjector.cs      |  401 +++---
 MelonLoader/Fixes/Il2CppInteropFixes.cs       | 1074 ++++++++---------
 MelonLoader/Fixes/ProcessFix.cs               |    1 +
 .../Fixes/ServerCertificateValidation.cs      |    1 +
 .../SharpZipLib/BZip2/BZip2InputStream.cs     |   42 +-
 .../SharpZipLib/BZip2/BZip2OutputStream.cs    |   86 +-
 .../SharpZipLib/Checksum/Adler32.cs           |    3 +
 .../Core/Exceptions/SharpZipBaseException.cs  |    2 +-
 .../Exceptions/StreamDecodingException.cs     |    2 +-
 .../Exceptions/StreamUnsupportedException.cs  |    2 +-
 .../UnexpectedEndOfStreamException.cs         |    2 +-
 .../Exceptions/ValueOutOfRangeException.cs    |    2 +-
 .../SharpZipLib/Core/FileSystemScanner.cs     |    2 +
 .../SharpZipLib/Core/NameFilter.cs            |    3 +
 .../SharpZipLib/Core/PathFilter.cs            |    3 +
 .../SharpZipLib/Core/StreamUtils.cs           |    2 +
 .../SharpZipLib/Encryption/PkzipClassic.cs    |    2 +
 .../SharpZipLib/Encryption/ZipAESStream.cs    |    3 +
 .../SharpZipLib/Encryption/ZipAESTransform.cs |    4 +
 .../SharpZipLib/GZip/GzipInputStream.cs       |    4 +
 .../ICSharpCode/SharpZipLib/Tar/TarArchive.cs |    7 +-
 .../ICSharpCode/SharpZipLib/Tar/TarBuffer.cs  |    2 +
 .../ICSharpCode/SharpZipLib/Tar/TarEntry.cs   |    3 +-
 .../ICSharpCode/SharpZipLib/Tar/TarHeader.cs  |   15 +-
 .../SharpZipLib/Tar/TarInputStream.cs         |   26 +-
 .../SharpZipLib/Tar/TarOutputStream.cs        |    5 +-
 .../SharpZipLib/Zip/Compression/Deflater.cs   |    6 +
 .../Zip/Compression/DeflaterEngine.cs         |   18 +-
 .../Zip/Compression/DeflaterHuffman.cs        |   21 +-
 .../SharpZipLib/Zip/Compression/Inflater.cs   |   22 +-
 .../Zip/Compression/InflaterHuffmanTree.cs    |    9 +
 .../Zip/Compression/PendingBuffer.cs          |    2 +
 .../Streams/DeflaterOutputStream.cs           |    2 +
 .../Streams/InflaterInputStream.cs            |   11 +-
 .../Zip/Compression/Streams/OutputWindow.cs   |    3 +
 .../Compression/Streams/StreamManipulator.cs  |    7 +
 .../ICSharpCode/SharpZipLib/Zip/FastZip.cs    |    7 +-
 .../SharpZipLib/Zip/WindowsNameTransform.cs   |    3 +
 .../ICSharpCode/SharpZipLib/Zip/ZipEntry.cs   |   33 +-
 .../SharpZipLib/Zip/ZipEntryFactory.cs        |    1 +
 .../SharpZipLib/Zip/ZipExtraData.cs           |   12 +
 .../ICSharpCode/SharpZipLib/Zip/ZipFile.cs    |   25 +-
 .../SharpZipLib/Zip/ZipHelperStream.cs        |    3 +
 .../SharpZipLib/Zip/ZipInputStream.cs         |   23 +-
 .../SharpZipLib/Zip/ZipNameTransform.cs       |    9 +-
 .../SharpZipLib/Zip/ZipOutputStream.cs        |   14 +-
 MelonLoader/InternalUtils/DependencyGraph.cs  |    2 +
 .../InternalUtils/Il2CppAssemblyGenerator.cs  |   67 +-
 .../InternalUtils/UnityInformationHandler.cs  |    2 +
 MelonLoader/LemonArraySegment.cs              |   13 +-
 MelonLoader/LemonEnumerator.cs                |    5 +-
 MelonLoader/MelonAction.cs                    |    1 +
 MelonLoader/MelonBase.cs                      |    7 +
 MelonLoader/MelonPreferences.cs               |   49 +-
 MelonLoader/MelonPreferences_Category.cs      |    4 +
 MelonLoader/MelonUtils.cs                     |   14 +-
 MelonLoader/Melons/MelonFolderHandler.cs      |    1 +
 MelonLoader/Modules/MelonModule.cs            |    1 +
 MelonLoader/NativeUtils/NativeHooks.cs        |    8 +-
 MelonLoader/Pastel/Pastel.cs                  |    1 +
 MelonLoader/Preferences/IO/Watcher.cs         |    2 +
 .../MelonPreferences_ReflectiveCategory.cs    |    4 +
 MelonLoader/RegisterTypeInIl2Cpp.cs           |    2 +-
 .../RegisterTypeInIl2CppWithInterfaces.cs     |    2 +-
 MelonLoader/Semver/IntExtensions.cs           |    8 +-
 MelonLoader/Semver/SemVersion.cs              |    8 +-
 MelonLoader/SupportModule.cs                  |    1 +
 MelonLoader/TinyJSON/Decoder.cs               |    4 +-
 MelonLoader/TinyJSON/Extensions.cs            |    4 +-
 MelonLoader/TinyJSON/JSON.cs                  |    3 +-
 MelonLoader/Utils/AssemblyVerifier.cs         |  282 ++---
 MelonLoader/Utils/SteamManifestReader.cs      |    2 +
 .../Il2CppAssetBundle.cs                      |   46 +-
 .../Il2CppAssetBundleRequest.cs               |    6 +-
 .../Il2CppImageConversionManager.cs           |    6 +-
 92 files changed, 1694 insertions(+), 1368 deletions(-)

diff --git a/Dependencies/CompatibilityLayers/IPA/Module.cs b/Dependencies/CompatibilityLayers/IPA/Module.cs
index be9faaa32..cc4ac9f58 100644
--- a/Dependencies/CompatibilityLayers/IPA/Module.cs
+++ b/Dependencies/CompatibilityLayers/IPA/Module.cs
@@ -52,6 +52,7 @@ private ResolvedMelons Resolve(Assembly asm)
             else
                 rotten.Add(rm);
         }
+
         return new ResolvedMelons(melons.ToArray(), rotten.ToArray());
     }
 
diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs
index cbee2ea5b..a31155428 100644
--- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs
+++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs
@@ -51,6 +51,7 @@ private ResolvedMelons Resolve(Assembly asm)
             else
                 rotten.Add(rm);
         }
+
         return new ResolvedMelons(melons.ToArray(), rotten.ToArray());
     }
 
diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs
index 41a4ad960..624742bc1 100644
--- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs
+++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/MuseDashModLoader/ModLoader.cs
@@ -33,6 +33,7 @@ public static void LoadDependency(Assembly assembly)
                     stream.Read(buffer, 0, buffer.Length);
                     dependAssembly = Assembly.Load(buffer);
                 }
+
                 depends.Add(dependName, dependAssembly);
             }
         }
diff --git a/Dependencies/Il2CppAssemblyGenerator/Core.cs b/Dependencies/Il2CppAssemblyGenerator/Core.cs
index 1519dab5d..d5b429a8f 100644
--- a/Dependencies/Il2CppAssemblyGenerator/Core.cs
+++ b/Dependencies/Il2CppAssemblyGenerator/Core.cs
@@ -97,6 +97,7 @@ private static int Run()
             Logger.Msg("Assembly is up to date. No Generation Needed.");
             return 0;
         }
+
         Logger.Msg("Assembly Generation Needed!");
 
         cpp2il.Cleanup();
@@ -143,6 +144,7 @@ private static void OldFiles_Cleanup()
                 File.Delete(filepath);
             }
         }
+
         Config.Values.OldFiles.Clear();
     }
 
@@ -162,6 +164,7 @@ private static void OldFiles_LAM()
             Directory.CreateDirectory(il2CppAssembliesDirectory);
             File.Move(filepath, newfilepath);
         }
+
         Config.Save();
     }
 }
\ No newline at end of file
diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs
index f9d2135d4..ab5d32510 100644
--- a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs
+++ b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationMap.cs
@@ -36,6 +36,7 @@ internal override bool ShouldSetup()
             if (!hashstr.Equals(Version, StringComparison.OrdinalIgnoreCase))
                 return true;
         }
+
         return false;
     }
 }
diff --git a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs
index 11294a616..6d3ee6aaa 100644
--- a/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs
+++ b/Dependencies/Il2CppAssemblyGenerator/Packages/DeobfuscationRegex.cs
@@ -28,6 +28,7 @@ internal void Setup()
                 Core.AssemblyGenerationNeeded = true;
                 return;
             }
+
             if (!Config.Values.DeobfuscationRegex.Equals(Regex))
             {
                 Core.AssemblyGenerationNeeded = true;
diff --git a/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs b/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs
index fa30e8f69..0714445ad 100644
--- a/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs
+++ b/Dependencies/SupportModules/Il2Cpp/MonoEnumeratorWrapper.cs
@@ -15,10 +15,10 @@ internal static unsafe void Register()
 
     private readonly IEnumerator enumerator;
     public MonoEnumeratorWrapper(IntPtr ptr) : base(ptr) { }
-    public MonoEnumeratorWrapper(IEnumerator _enumerator) : base(ClassInjector.DerivedConstructorPointer())
+    public MonoEnumeratorWrapper(IEnumerator enumerator) : base(ClassInjector.DerivedConstructorPointer())
     {
         ClassInjector.DerivedConstructorBody(this);
-        enumerator = _enumerator ?? throw new NullReferenceException("routine is null");
+        this.enumerator = enumerator ?? throw new NullReferenceException("routine is null");
     }
 
     public Il2CppSystem.Object /*IEnumerator.*/Current
diff --git a/Dependencies/SupportModules/SceneHandler.cs b/Dependencies/SupportModules/SceneHandler.cs
index 230327d83..a2743d980 100644
--- a/Dependencies/SupportModules/SceneHandler.cs
+++ b/Dependencies/SupportModules/SceneHandler.cs
@@ -93,6 +93,7 @@ internal static void OnUpdate()
                     requeue.Enqueue(evt);
                 }
             }
+
             while ((requeue.Count > 0) && ((evt = requeue.Dequeue()) != null))
                 scenesLoaded.Enqueue(evt);
         }
diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs
index c2f9389af..cfb58f0a2 100644
--- a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs
+++ b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs
@@ -60,6 +60,7 @@ public static GetterHandler CreateFieldGetter(Type type, params string[] names)
             if (property is not null)
                 return CreateGetterHandler(property);
         }
+
         return null;
     }
 
diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs
index dca2c9007..e2271ec28 100644
--- a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs
+++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs
@@ -40,8 +40,10 @@ public static Dictionary> GetPrefere
                 var newpref = new MelonPreference(entry);
                 newprefsdict.Add(entry.Identifier, newpref);
             }
+
             output.Add(category.Identifier, newprefsdict);
         }
+
         return output;
     }
     [Obsolete("MelonLoader.MelonPrefs.GetCategoryDisplayName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetCategoryDisplayName instead.")]
diff --git a/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs b/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs
index 1d1f4ccc9..b90c76c89 100644
--- a/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs
+++ b/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs
@@ -26,8 +26,10 @@ public static Dictionary> GetPrefs()
                 };
                 newprefsdict.Add(prefsdict.Keys.ElementAt(j), newpref);
             }
+
             output.Add(prefs.Keys.ElementAt(i), newprefsdict);
         }
+
         return output;
     }
     [Obsolete("MelonLoader.ModPrefs.RegisterPrefString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")]
diff --git a/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs b/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs
index ebb3a09ba..2500a98d6 100644
--- a/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs
+++ b/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs
@@ -8,119 +8,120 @@
 using System.Reflection.Emit;
 using System.Runtime.InteropServices;
 
-namespace MelonLoader.CoreClrUtils
+namespace MelonLoader.CoreClrUtils;
+
+internal class CoreClrDelegateFixer
 {
-    internal class CoreClrDelegateFixer
+    private static readonly AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new("MelonLoaderFixedHooks"), AssemblyBuilderAccess.Run);
+    private static readonly ModuleBuilder module = assembly.DefineDynamicModule("MelonLoaderFixedHooks");
+    private static readonly List PinnedFixedDelegates = [];
+
+    internal static bool SanityCheckDetour(ref IntPtr detour)
     {
-        private static readonly AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new("MelonLoaderFixedHooks"), AssemblyBuilderAccess.Run);
-        private static readonly ModuleBuilder module = assembly.DefineDynamicModule("MelonLoaderFixedHooks");
-        private static readonly List PinnedFixedDelegates = new List();
+        using var dt = DataTarget.CreateSnapshotAndAttach(Environment.ProcessId);
+        var runtime = dt.ClrVersions.First().CreateRuntime();
 
-        internal static bool SanityCheckDetour(ref IntPtr detour)
-        {
-            using DataTarget dt = DataTarget.CreateSnapshotAndAttach(Environment.ProcessId);
-            ClrRuntime runtime = dt.ClrVersions.First().CreateRuntime();
+        var method = runtime.GetMethodByInstructionPointer((ulong)detour.ToInt64());
 
-            ClrMethod method = runtime.GetMethodByInstructionPointer((ulong)detour.ToInt64());
+        if (method != null)
+        {
+            var managedMethod = MethodBaseHelper.GetMethodBaseFromHandle((IntPtr)method.MethodDesc);
 
-            if (method != null)
+            if (managedMethod?.GetCustomAttribute() == null)
             {
-                var managedMethod = MethodBaseHelper.GetMethodBaseFromHandle((IntPtr)method.MethodDesc);
-
-                if (managedMethod?.GetCustomAttribute() == null)
-                {
-                    //We have provided a direct managed method as the pointer to detour to. This doesn't work under CoreCLR, so we yell at the user and stop
-                    var melon = MelonUtils.GetMelonFromStackTrace(new System.Diagnostics.StackTrace(), true);
+                //We have provided a direct managed method as the pointer to detour to. This doesn't work under CoreCLR, so we yell at the user and stop
+                var melon = MelonUtils.GetMelonFromStackTrace(new System.Diagnostics.StackTrace(), true);
 
-                    var logger = melon?.LoggerInstance ?? new MelonLogger.Instance("Bad Delegate");
-                    var modName = melon?.Info.Name ?? "Unknown mod";
+                var logger = melon?.LoggerInstance ?? new MelonLogger.Instance("Bad Delegate");
+                var modName = melon?.Info.Name ?? "Unknown mod";
 
-                    //Try and patch the delegate if we can
-                    if(melon != null && managedMethod is MethodInfo methodInfo)
+                //Try and patch the delegate if we can
+                if (melon != null && managedMethod is MethodInfo methodInfo)
+                {
+                    try
                     {
-                        try
-                        {
-                            var wrapperType = GetHookWrapperDelegateType(melon, methodInfo);
+                        var wrapperType = GetHookWrapperDelegateType(melon, methodInfo);
 
-                            var del = Delegate.CreateDelegate(wrapperType, methodInfo);
-                            PinnedFixedDelegates.Add(del);
+                        var del = Delegate.CreateDelegate(wrapperType, methodInfo);
+                        PinnedFixedDelegates.Add(del);
 
-                            detour = Marshal.GetFunctionPointerForDelegate(del);
+                        detour = Marshal.GetFunctionPointerForDelegate(del);
 
-                            logger.Warning($"Encountered a dodgy native hook to a managed method in melon {modName}: {methodInfo.DeclaringType.FullName}::{methodInfo.Name}. It has been wrapped in a proper unmanaged delegate, but please fix your mod! You also won't be able to detach this hook!");
+                        logger.Warning($"Encountered a dodgy native hook to a managed method in melon {modName}: {methodInfo.DeclaringType.FullName}::{methodInfo.Name}. It has been wrapped in a proper unmanaged delegate, but please fix your mod! You also won't be able to detach this hook!");
 
-                            return true;
-                        } catch(Exception ex)
-                        {
-                            MelonLogger.Error("Failed to repair invalid native hook: ", ex);
-                            //Ignore, fall down to error below
-                        }
-                    } else
+                        return true;
+                    }
+                    catch (Exception ex)
                     {
-                        logger.Error($"Failed to resolve the offending melon from the stack and/or the managed method target. ManagedMethod is {managedMethod}, of type {managedMethod.GetType()}, stack is {Environment.StackTrace}");
+                        MelonLogger.Error("Failed to repair invalid native hook: ", ex);
+                        //Ignore, fall down to error below
                     }
-
-                    PrintDirtyDelegateWarning(logger, modName, managedMethod);
-                    return false;
                 }
-            }
+                else
+                {
+                    logger.Error($"Failed to resolve the offending melon from the stack and/or the managed method target. ManagedMethod is {managedMethod}, of type {managedMethod.GetType()}, stack is {Environment.StackTrace}");
+                }
 
-            return true;
+                PrintDirtyDelegateWarning(logger, modName, managedMethod);
+                return false;
+            }
         }
 
-        private static Type GetHookWrapperDelegateType(MelonBase melon, MethodInfo managedMethod)
-        {
-            var methodId = $"{melon.Info.Name.Replace(' ', '_')}_{managedMethod.DeclaringType.Namespace.Replace('.', '_')}_{managedMethod.Name}";
-            var typeName = $"BrokenHookWrapperDelegate_{methodId}";
-
-            if (module.GetType(typeName) is { } ret)
-                return ret;
-
-            var type = module.DefineType(typeName, TypeAttributes.Sealed | TypeAttributes.Public, typeof(MulticastDelegate));
-            type.SetCustomAttribute(new CustomAttributeBuilder(typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new[] { typeof(CallingConvention) }), new object[] { CallingConvention.Cdecl }));
-
-            var ctor = type.DefineConstructor(MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Public, CallingConventions.HasThis, new[] { typeof(object), typeof(IntPtr) });
-            ctor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
-
-            var parameterTypes = managedMethod.GetParameters().Select(p => p.ParameterType).ToArray();
-
-            //We assume that the hook has the correct signature and just copy over its params
-            type.DefineMethod(
-                "Invoke",
-                MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public,
-                CallingConventions.HasThis,
-                managedMethod.ReturnType,
-                parameterTypes
-            ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
-
-            type.DefineMethod(
-                "BeginInvoke",
-                MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public,
-                CallingConventions.HasThis, typeof(IAsyncResult),
-                parameterTypes.Concat(new[] { typeof(AsyncCallback), typeof(object) }).ToArray()
-            ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
-
-            type.DefineMethod(
-                "EndInvoke",
-                MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public,
-                CallingConventions.HasThis,
-                managedMethod.ReturnType,
-                new[] { typeof(IAsyncResult) }
-            ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
-
-            return type.CreateType();
-        }
+        return true;
+    }
 
-        private static void PrintDirtyDelegateWarning(MelonLogger.Instance offendingMelonLogger, string offendingMelonName, MethodBase offendingMethod)
-        {
-            offendingMelonLogger.BigError(
-                   $"The mod {offendingMelonName} has attempted to detour a native method to a managed one.\n"
-                 + $"The managed method target is {offendingMethod.DeclaringType.Name}::{offendingMethod.Name}\n"
-                 + "If this hadn't been stopped, the runtime would have crashed.\n"
-                 + "Modder: Either create an [UnmanagedFunctionPointer] delegate from your function, and use Marshal.GetFunctionPointerFromDelegate,\n"
-                 + "or annotate your patch function as [UnmanagedCallersOnly] (target net5.0), and then you can directly use &Method as the hook target."
-            );
-        }
+    private static Type GetHookWrapperDelegateType(MelonBase melon, MethodInfo managedMethod)
+    {
+        var methodId = $"{melon.Info.Name.Replace(' ', '_')}_{managedMethod.DeclaringType.Namespace.Replace('.', '_')}_{managedMethod.Name}";
+        var typeName = $"BrokenHookWrapperDelegate_{methodId}";
+
+        if (module.GetType(typeName) is { } ret)
+            return ret;
+
+        var type = module.DefineType(typeName, TypeAttributes.Sealed | TypeAttributes.Public, typeof(MulticastDelegate));
+        type.SetCustomAttribute(new CustomAttributeBuilder(typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new[] { typeof(CallingConvention) }), new object[] { CallingConvention.Cdecl }));
+
+        var ctor = type.DefineConstructor(MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Public, CallingConventions.HasThis, new[] { typeof(object), typeof(IntPtr) });
+        ctor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+        var parameterTypes = managedMethod.GetParameters().Select(p => p.ParameterType).ToArray();
+
+        //We assume that the hook has the correct signature and just copy over its params
+        type.DefineMethod(
+            "Invoke",
+            MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public,
+            CallingConventions.HasThis,
+            managedMethod.ReturnType,
+            parameterTypes
+        ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+        type.DefineMethod(
+            "BeginInvoke",
+            MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public,
+            CallingConventions.HasThis, typeof(IAsyncResult),
+            parameterTypes.Concat(new[] { typeof(AsyncCallback), typeof(object) }).ToArray()
+        ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+        type.DefineMethod(
+            "EndInvoke",
+            MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public,
+            CallingConventions.HasThis,
+            managedMethod.ReturnType,
+            new[] { typeof(IAsyncResult) }
+        ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+        return type.CreateType();
+    }
+
+    private static void PrintDirtyDelegateWarning(MelonLogger.Instance offendingMelonLogger, string offendingMelonName, MethodBase offendingMethod)
+    {
+        offendingMelonLogger.BigError(
+               $"The mod {offendingMelonName} has attempted to detour a native method to a managed one.\n"
+             + $"The managed method target is {offendingMethod.DeclaringType.Name}::{offendingMethod.Name}\n"
+             + "If this hadn't been stopped, the runtime would have crashed.\n"
+             + "Modder: Either create an [UnmanagedFunctionPointer] delegate from your function, and use Marshal.GetFunctionPointerFromDelegate,\n"
+             + "or annotate your patch function as [UnmanagedCallersOnly] (target net5.0), and then you can directly use &Method as the hook target."
+        );
     }
 }
 #endif
\ No newline at end of file
diff --git a/MelonLoader/CoreClrUtils/MethodBaseHelper.cs b/MelonLoader/CoreClrUtils/MethodBaseHelper.cs
index c2f012a72..ceb1ad200 100644
--- a/MelonLoader/CoreClrUtils/MethodBaseHelper.cs
+++ b/MelonLoader/CoreClrUtils/MethodBaseHelper.cs
@@ -7,40 +7,39 @@
 
 #nullable enable
 
-namespace MelonLoader.CoreClrUtils
+namespace MelonLoader.CoreClrUtils;
+
+internal static class MethodBaseHelper
 {
-    internal static class MethodBaseHelper
-    {
-        private static Type? RuntimeMethodHandleInternal;
-        private static ConstructorInfo? RuntimeMethodHandleInternal_Constructor;
-        private static Type? RuntimeType;
-        private static MethodInfo? RuntimeType_GetMethodBase;
+    private static Type? RuntimeMethodHandleInternal;
+    private static ConstructorInfo? RuntimeMethodHandleInternal_Constructor;
+    private static Type? RuntimeType;
+    private static MethodInfo? RuntimeType_GetMethodBase;
 
-        public static MethodBase? GetMethodBaseFromHandle(IntPtr handle)
-        {
-            RuntimeMethodHandleInternal ??= typeof(RuntimeMethodHandle).Assembly.GetType("System.RuntimeMethodHandleInternal", throwOnError: true)!;
-            RuntimeMethodHandleInternal_Constructor ??= RuntimeMethodHandleInternal.GetConstructor
-            (
-                BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DoNotWrapExceptions,
-                binder: null,
-                new[] { typeof(IntPtr) },
-                modifiers: null
-            ) ?? throw new InvalidOperationException("RuntimeMethodHandleInternal constructor is missing!");
+    public static MethodBase? GetMethodBaseFromHandle(IntPtr handle)
+    {
+        RuntimeMethodHandleInternal ??= typeof(RuntimeMethodHandle).Assembly.GetType("System.RuntimeMethodHandleInternal", throwOnError: true)!;
+        RuntimeMethodHandleInternal_Constructor ??= RuntimeMethodHandleInternal.GetConstructor
+        (
+            BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DoNotWrapExceptions,
+            binder: null,
+            new[] { typeof(IntPtr) },
+            modifiers: null
+        ) ?? throw new InvalidOperationException("RuntimeMethodHandleInternal constructor is missing!");
 
-            RuntimeType ??= typeof(Type).Assembly.GetType("System.RuntimeType", throwOnError: true)!;
-            RuntimeType_GetMethodBase ??= RuntimeType.GetMethod
-            (
-                "GetMethodBase",
-                BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DoNotWrapExceptions,
-                binder: null,
-                new[] { RuntimeType, RuntimeMethodHandleInternal },
-                modifiers: null
-            ) ?? throw new InvalidOperationException("RuntimeType.GetMethodBase is missing!");
+        RuntimeType ??= typeof(Type).Assembly.GetType("System.RuntimeType", throwOnError: true)!;
+        RuntimeType_GetMethodBase ??= RuntimeType.GetMethod
+        (
+            "GetMethodBase",
+            BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DoNotWrapExceptions,
+            binder: null,
+            new[] { RuntimeType, RuntimeMethodHandleInternal },
+            modifiers: null
+        ) ?? throw new InvalidOperationException("RuntimeType.GetMethodBase is missing!");
 
-            // Wrap the handle
-            object runtimeHandle = RuntimeMethodHandleInternal_Constructor.Invoke(new[] { (object)handle });
-            return (MethodBase?)RuntimeType_GetMethodBase.Invoke(null, new[] { null, runtimeHandle });
-        }
+        // Wrap the handle
+        var runtimeHandle = RuntimeMethodHandleInternal_Constructor.Invoke(new[] { (object)handle });
+        return (MethodBase?)RuntimeType_GetMethodBase.Invoke(null, new[] { null, runtimeHandle });
     }
 }
 #endif
\ No newline at end of file
diff --git a/MelonLoader/CoreClrUtils/NativeStackWalk.cs b/MelonLoader/CoreClrUtils/NativeStackWalk.cs
index b762dcca8..6826be0c8 100644
--- a/MelonLoader/CoreClrUtils/NativeStackWalk.cs
+++ b/MelonLoader/CoreClrUtils/NativeStackWalk.cs
@@ -4,6 +4,8 @@
 #pragma warning disable CS0169
 #pragma warning disable CS0649
 
+using MelonLoader.Utils;
+using Microsoft.Diagnostics.Runtime;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -15,17 +17,15 @@
 using System.Runtime.InteropServices;
 using System.Runtime.Versioning;
 using System.Text;
-using MelonLoader.Utils;
-using Microsoft.Diagnostics.Runtime;
 
 namespace MelonLoader.CoreClrUtils;
 
 // ReSharper disable UnusedMember.Local, InconsistentNaming
 public static unsafe class NativeStackWalk
 {
-    private static MelonLogger.Instance Logger = new MelonLogger.Instance("NativeStackWalk", Color.GreenYellow);
-    
-    private static Dictionary _registeredHooks = new();
+    private static readonly MelonLogger.Instance Logger = new("NativeStackWalk", Color.GreenYellow);
+
+    private static readonly Dictionary _registeredHooks = [];
 
     #region Native Structs
 
@@ -90,7 +90,7 @@ private struct THREAD_BASIC_INFORMATION
 
     private struct NT_TIB
     {
-        void* ExceptionList;
+        private readonly void* ExceptionList;
         public void* StackBase;
 
         public void* StackLimit;
@@ -229,28 +229,28 @@ private struct SYMBOL_INFO
     [DllImport("dbghelp.dll", SetLastError = true)]
     [return: MarshalAs(UnmanagedType.Bool)]
     private static extern bool SymFromAddrW(void* hProcess, ulong Address, ulong* Displacement, SYMBOL_INFO* Symbol);
-    
+
     //SymSetoptions import
     [DllImport("dbghelp.dll", SetLastError = true)]
     [return: MarshalAs(UnmanagedType.Bool)]
     private static extern bool SymSetOptions(SymOptions SymOptions);
-    
+
     [DllImport("dbghelp.dll", SetLastError = true)]
     private static extern ulong SymGetModuleBase64(void* hProcess, ulong Address);
 
     [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
     [return: MarshalAs(UnmanagedType.Bool)]
     private static extern bool SymCleanup(void* hProcess);
-    
+
     //GetModuleFileNameEx import
     [DllImport("psapi.dll", SetLastError = true)]
     [return: MarshalAs(UnmanagedType.Bool)]
     private static extern bool GetModuleFileNameEx(void* hProcess, void* hModule, [Out] char* lpFilename, uint nSize);
 
     #endregion
-    
+
     #region Helper Methods
-    
+
     [MethodImpl(MethodImplOptions.NoOptimization)]
     private static bool IsValidAddress(ulong addr, bool is64Bit) => !IsBadReadPtr((void*)(addr - 6), 7) && (!is64Bit || addr > 0x70000000000);
 
@@ -308,7 +308,7 @@ private static bool IsReturnAddress(ulong addr, bool is64Bit, out ulong calledAd
         calledAddr = 0;
         if (!IsValidAddress(addr, is64Bit))
             return false;
-        
+
         var cursor = (byte*)addr;
         if (cursor[-5] == 0xE8)
         {
@@ -378,7 +378,7 @@ private static bool IsReturnAddress(ulong addr, bool is64Bit, out ulong calledAd
         calledAddr = 0;
         return false;
     }
-    
+
     private static (IntPtr top, IntPtr end) GetStackBounds()
     {
         var tbi = stackalloc THREAD_BASIC_INFORMATION[1];
@@ -403,7 +403,7 @@ private static (IntPtr top, IntPtr end) GetStackBounds()
         }
 
         NativeMemory.Free(ctxData);
-        return ((IntPtr) top, (IntPtr) end);
+        return ((IntPtr)top, (IntPtr)end);
     }
 
     private static int InitDbgHelp(void* handle)
@@ -419,25 +419,20 @@ private static int InitDbgHelp(void* handle)
 
         SymSetOptions(SymOptions.SYMOPT_UNDNAME | SymOptions.SYMOPT_DEFERRED_LOADS);
 
-        if (!SymInitialize(handle, userPath, 1))
-        {
-            return Marshal.GetLastWin32Error();
-        }
-
-        return 0;
+        return !SymInitialize(handle, userPath, 1) ? Marshal.GetLastWin32Error() : 0;
     }
 
     private static string? GetHookName(ulong ip)
     {
         foreach (var (addr, name) in _registeredHooks)
         {
-            if (Math.Abs((long) ip - (long) addr) < 0x400)
+            if (Math.Abs((long)ip - (long)addr) < 0x400)
                 return name;
         }
 
         return null;
     }
-    
+
     #endregion
     public class NativeStackFrame
     {
@@ -467,10 +462,10 @@ public static List GetNativeStackFrames()
     {
         //Get the top and bottom of the managed stack
         var (t, e) = GetStackBounds();
-        
-        if(t == IntPtr.Zero || e == IntPtr.Zero)
+
+        if (t == IntPtr.Zero || e == IntPtr.Zero)
             throw new("Failed to get stack bounds");
-        
+
         var top = (nint)t;
         var end = (nint)e;
 
@@ -485,8 +480,8 @@ public static List GetNativeStackFrames()
         {
             var addr = *(ulong*)current;
             current += 4;
-            
-            if (!IsReturnAddress(addr, is64bit, out _)) 
+
+            if (!IsReturnAddress(addr, is64bit, out _))
                 continue;
 
             addresses.Add((nuint)addr);
@@ -495,7 +490,7 @@ public static List GetNativeStackFrames()
         var handle = (void*)Process.GetCurrentProcess().Handle;
 
         //Initialize DbgHelp to get symbol names
-        
+
         int initResult;
         if ((initResult = InitDbgHelp(handle)) != 0)
         {
@@ -505,13 +500,13 @@ public static List GetNativeStackFrames()
         var symSize = sizeof(SYMBOL_INFO);
         var displacement = 0ul;
         var ret = new List();
-        
+
         //Try to get information for each frame in the stack
         foreach (var address in addresses)
         {
             var frame = new NativeStackFrame { Pointer = address };
             ret.Add(frame);
-            
+
             var moduleBase = SymGetModuleBase64(handle, address);
 
             if (moduleBase == 0)
@@ -535,7 +530,7 @@ public static List GetNativeStackFrames()
                     }
                 }
 
-                if (frame.Function == null && GetHookName(address) is {} hookName)
+                if (frame.Function == null && GetHookName(address) is { } hookName)
                 {
                     frame.Function = $"(detour) {hookName}";
                     frame.ModulePath = "(injected code)";
@@ -543,17 +538,18 @@ public static List GetNativeStackFrames()
             }
 
             //Get the path of the module from its base address
-            var moduleNameBuff = (char*) NativeMemory.Alloc(256 * sizeof(char));
+            var moduleNameBuff = (char*)NativeMemory.Alloc(256 * sizeof(char));
             if (GetModuleFileNameEx(handle, (void*)moduleBase, moduleNameBuff, 256))
             {
                 var moduleName = Marshal.PtrToStringAnsi((IntPtr)moduleNameBuff);
                 frame.ModulePath = moduleName;
             }
+
             NativeMemory.Free(moduleNameBuff);
-            
+
             //Attempt to load actual symbol information via dbghelp
             var maxNameLen = 255u;
-            var symbol = (SYMBOL_INFO*) NativeMemory.Alloc((nuint)(symSize + maxNameLen * sizeof(char)));
+            var symbol = (SYMBOL_INFO*)NativeMemory.Alloc((nuint)(symSize + (maxNameLen * sizeof(char))));
 
             symbol->SizeOfStruct = (uint)symSize;
             symbol->MaxNameLen = maxNameLen;
@@ -566,7 +562,7 @@ public static List GetNativeStackFrames()
                 frame.Function = Encoding.Unicode.GetString(nameBuffer);
                 frame.Offset = displacement;
             }
-            
+
             NativeMemory.Free(symbol);
         }
 
@@ -582,7 +578,7 @@ public static string NativeStackTrace
         get
         {
             var sb = new StringBuilder();
-            
+
             GetNativeStackFrames().ForEach(f => sb.AppendLine(FormatFrame(f)));
 
             return sb.ToString();
@@ -592,12 +588,12 @@ public static string NativeStackTrace
     private static string FormatFrame(NativeStackFrame frame)
     {
         var frameBuilder = new StringBuilder();
-        
+
         if (frame.Function == null)
         {
             frameBuilder.Append($"at 0x{frame.Pointer:X}");
-            
-            if(frame.ModuleName is {} mn)
+
+            if (frame.ModuleName is { } mn)
                 frameBuilder.Append($" in {mn}");
             else
                 frameBuilder.Append(" (unknown module)");
@@ -621,7 +617,7 @@ private static string FormatFrame(NativeStackFrame frame)
             }
             else
                 frameBuilder.Append($"0x{frame.Pointer:X} ");
-            
+
             if (!isSymbolAccurate || !isGa)
                 frameBuilder.Append($" in {frame.ModuleName ?? ""}");
         }
diff --git a/MelonLoader/Fixes/AsmResolverFix.cs b/MelonLoader/Fixes/AsmResolverFix.cs
index 27ac59096..e35ddf5f4 100644
--- a/MelonLoader/Fixes/AsmResolverFix.cs
+++ b/MelonLoader/Fixes/AsmResolverFix.cs
@@ -1,40 +1,39 @@
 #if NET6_0_OR_GREATER
 
+using AsmResolver.PE.DotNet.Metadata;
+using HarmonyLib;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection.Emit;
-using AsmResolver.PE.DotNet.Metadata;
-using HarmonyLib;
 using MethodInfo = System.Reflection.MethodInfo;
 
-namespace MelonLoader.Fixes
+namespace MelonLoader.Fixes;
+
+public static class AsmResolverFix
 {
-    public static class AsmResolverFix
+    //adds https://github.com/Washi1337/AsmResolver/pull/609
+    public static void Install()
     {
-        //adds https://github.com/Washi1337/AsmResolver/pull/609
-        public static void Install()
-        {
-            MelonDebug.Msg("Patching AsmResolver SerializedTableStream GetCodedIndexSize...");
-            MethodInfo methodInfo = AccessTools.Method(typeof(SerializedTableStream).GetNestedType("<>c__DisplayClass18_0", AccessTools.all), "b__1");
-            Core.HarmonyInstance.Patch(methodInfo, null, null, new HarmonyMethod(typeof(AsmResolverFix).GetMethod(nameof(GetCodexIndexSizePatch))));
-        }
+        MelonDebug.Msg("Patching AsmResolver SerializedTableStream GetCodedIndexSize...");
+        var methodInfo = AccessTools.Method(typeof(SerializedTableStream).GetNestedType("<>c__DisplayClass18_0", AccessTools.all), "b__1");
+        Core.HarmonyInstance.Patch(methodInfo, null, null, new HarmonyMethod(typeof(AsmResolverFix).GetMethod(nameof(GetCodexIndexSizePatch))));
+    }
 
-        public static IEnumerable GetCodexIndexSizePatch(IEnumerable instructions)
+    public static IEnumerable GetCodexIndexSizePatch(IEnumerable instructions)
+    {
+        var codes = instructions.ToList();
+        for (var i = 0; i < codes.Count; i++)
         {
-            List codes = instructions.ToList();
-            for (int i = 0; i < codes.Count; i++)
+            if (codes[i].opcode == OpCodes.Clt)
             {
-                if (codes[i].opcode == OpCodes.Clt)
-                {
-                    codes[i].opcode = OpCodes.Cgt;
-                    codes.Insert(i + 1, new CodeInstruction(OpCodes.Ldc_I4_0));
-                    codes.Insert(i + 2, new CodeInstruction(OpCodes.Ceq)); 
-                    break;
-                }
+                codes[i].opcode = OpCodes.Cgt;
+                codes.Insert(i + 1, new CodeInstruction(OpCodes.Ldc_I4_0));
+                codes.Insert(i + 2, new CodeInstruction(OpCodes.Ceq));
+                break;
             }
-            return codes;
         }
 
+        return codes;
     }
 }
 #endif
diff --git a/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs b/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs
index a5ee6745d..27edbe1a4 100644
--- a/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs
+++ b/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs
@@ -8,114 +8,114 @@
 using System.Runtime.InteropServices;
 using System.Runtime.Loader;
 
-namespace MelonLoader.Fixes
-{
-    internal class DotnetAssemblyLoadContextFix
-    {
-        private delegate Assembly DelegateInternalLoad(ReadOnlySpan arrAssembly, ReadOnlySpan arrSymbols);
+namespace MelonLoader.Fixes;
 
-        private static readonly Dictionary s_loadfile = new Dictionary();
+internal class DotnetAssemblyLoadContextFix
+{
+    private delegate Assembly DelegateInternalLoad(ReadOnlySpan arrAssembly, ReadOnlySpan arrSymbols);
 
-        private static readonly MethodInfo AlcInternalLoad = typeof(AssemblyLoadContext).GetMethod("InternalLoad", BindingFlags.NonPublic | BindingFlags.Instance);
-        private static readonly MethodInfo AlcQCallLoadFromPath = typeof(AssemblyLoadContext).GetMethod("LoadFromPath", BindingFlags.NonPublic | BindingFlags.Static);
-        private static readonly MethodInfo AlcQCallLoadFromStream = typeof(AssemblyLoadContext).GetMethod("LoadFromStream", BindingFlags.NonPublic | BindingFlags.Static);
+    private static readonly Dictionary s_loadfile = [];
 
-        private static DelegateInternalLoad DefaultContextInternalLoad = AlcInternalLoad.CreateDelegate(AssemblyLoadContext.Default);
+    private static readonly MethodInfo AlcInternalLoad = typeof(AssemblyLoadContext).GetMethod("InternalLoad", BindingFlags.NonPublic | BindingFlags.Instance);
+    private static readonly MethodInfo AlcQCallLoadFromPath = typeof(AssemblyLoadContext).GetMethod("LoadFromPath", BindingFlags.NonPublic | BindingFlags.Static);
+    private static readonly MethodInfo AlcQCallLoadFromStream = typeof(AssemblyLoadContext).GetMethod("LoadFromStream", BindingFlags.NonPublic | BindingFlags.Static);
 
+    private static readonly DelegateInternalLoad DefaultContextInternalLoad = AlcInternalLoad.CreateDelegate(AssemblyLoadContext.Default);
 
-        internal static void Install()
+    internal static void Install()
+    {
+        try
         {
-            try
-            {
-                Core.HarmonyInstance.Patch(AccessTools.Method(typeof(Assembly), nameof(Assembly.Load), new Type[] { typeof(byte[]), typeof(byte[]) }), new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAssemblyLoad)));
-                Core.HarmonyInstance.Patch(AccessTools.Method(typeof(Assembly), nameof(Assembly.LoadFile)), new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAssemblyLoadFile)));
-
-                //We have to load everything required for the verifier to avoid getting stuck in an infinite loop, prior to hooking AssemblyLoadContext.
-                AssemblyVerifier.EnsureInitialized();
-
-                //Now hook ALC.
-                Core.HarmonyInstance.Patch(AlcQCallLoadFromPath, new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAlcLoadFromPath)));
-                Core.HarmonyInstance.Patch(AlcQCallLoadFromStream, new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAlcLoadFromStream)));
-            }
-            catch (Exception ex) { MelonLogger.Warning($"DotnetAssemblyLoadContextFix Exception: {ex}"); }
-        }
+            Core.HarmonyInstance.Patch(AccessTools.Method(typeof(Assembly), nameof(Assembly.Load), new Type[] { typeof(byte[]), typeof(byte[]) }), new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAssemblyLoad)));
+            Core.HarmonyInstance.Patch(AccessTools.Method(typeof(Assembly), nameof(Assembly.LoadFile)), new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAssemblyLoadFile)));
 
-        public static bool PreAssemblyLoad(byte[] rawAssembly, byte[] rawSymbolStore, ref Assembly __result)
-        {
-            //if(MelonDebug.IsEnabled() && !Environment.StackTrace.Contains("HarmonyLib"))
-            //    MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.Load call with {rawAssembly.Length}-byte assembly to AssemblyLoadContext.Default. Mod Devs: You may wish to use this explictly.");
+            //We have to load everything required for the verifier to avoid getting stuck in an infinite loop, prior to hooking AssemblyLoadContext.
+            AssemblyVerifier.EnsureInitialized();
 
-            var (ok, reason) = AssemblyVerifier.VerifyByteArray(rawAssembly);
-            if (!ok)
-            {
-                throw new BadImageFormatException();
-            }
+            //Now hook ALC.
+            Core.HarmonyInstance.Patch(AlcQCallLoadFromPath, new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAlcLoadFromPath)));
+            Core.HarmonyInstance.Patch(AlcQCallLoadFromStream, new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAlcLoadFromStream)));
+        }
+        catch (Exception ex)
+        {
+            MelonLogger.Warning($"DotnetAssemblyLoadContextFix Exception: {ex}");
+        }
+    }
 
-            __result = DefaultContextInternalLoad(rawAssembly, rawSymbolStore);
+    public static bool PreAssemblyLoad(byte[] rawAssembly, byte[] rawSymbolStore, ref Assembly __result)
+    {
+        //if(MelonDebug.IsEnabled() && !Environment.StackTrace.Contains("HarmonyLib"))
+        //    MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.Load call with {rawAssembly.Length}-byte assembly to AssemblyLoadContext.Default. Mod Devs: You may wish to use this explictly.");
 
-            //Prevent loading in non-default context, which is default behaviour of Assembly.Load
-            return false;
+        var (ok, reason) = AssemblyVerifier.VerifyByteArray(rawAssembly);
+        if (!ok)
+        {
+            throw new BadImageFormatException();
         }
 
-        public static bool PreAssemblyLoadFile(string path, ref Assembly __result)
-        {
-            //MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.LoadFile({path}) call to AssemblyLoadContext.Default.LoadFromAssemblyPath. Mod Devs: You may wish to use this explictly.");
+        __result = DefaultContextInternalLoad(rawAssembly, rawSymbolStore);
 
-            string normalizedPath = Path.GetFullPath(path);
+        //Prevent loading in non-default context, which is default behaviour of Assembly.Load
+        return false;
+    }
+
+    public static bool PreAssemblyLoadFile(string path, ref Assembly __result)
+    {
+        //MelonDebug.Msg($"[.NET AssemblyLoadContext Fix] Redirecting Assembly.LoadFile({path}) call to AssemblyLoadContext.Default.LoadFromAssemblyPath. Mod Devs: You may wish to use this explictly.");
 
-            //Don't need to verify here, as we're passing to the ALC.
-            //var (ok, reason) = AssemblyVerifier.VerifyFile(normalizedPath);
-            //if (!ok)
-            //{
-            //    throw new BadImageFormatException();
-            //}
+        var normalizedPath = Path.GetFullPath(path);
 
-            lock (s_loadfile)
-            {
-                if (s_loadfile.TryGetValue(normalizedPath, out __result))
-                    return false;
+        //Don't need to verify here, as we're passing to the ALC.
+        //var (ok, reason) = AssemblyVerifier.VerifyFile(normalizedPath);
+        //if (!ok)
+        //{
+        //    throw new BadImageFormatException();
+        //}
 
-                __result = AssemblyLoadContext.Default.LoadFromAssemblyPath(normalizedPath);
+        lock (s_loadfile)
+        {
+            if (s_loadfile.TryGetValue(normalizedPath, out __result))
+                return false;
 
-                s_loadfile.Add(normalizedPath, __result);
-            }
+            __result = AssemblyLoadContext.Default.LoadFromAssemblyPath(normalizedPath);
 
-            return false;
+            s_loadfile.Add(normalizedPath, __result);
         }
 
-        public static bool PreAlcLoadFromPath(string ilPath)
-        {
-            //MelonDebug.Msg($"[ALC FromPath] Validating {ilPath}...");
+        return false;
+    }
 
-            //Simple pass-to-verifier and throw if bad.
-            var (ok, reason) = AssemblyVerifier.VerifyFile(ilPath);
-            if (!ok)
-            {
-                throw new BadImageFormatException();
-            }
+    public static bool PreAlcLoadFromPath(string ilPath)
+    {
+        //MelonDebug.Msg($"[ALC FromPath] Validating {ilPath}...");
 
-            //Continue to run the original runtime QCall.
-            return true;
+        //Simple pass-to-verifier and throw if bad.
+        var (ok, _) = AssemblyVerifier.VerifyFile(ilPath);
+        if (!ok)
+        {
+            throw new BadImageFormatException();
         }
 
-        public static unsafe bool PreAlcLoadFromStream(IntPtr ptrAssemblyArray, int iAssemblyArrayLen)
-        {
-            //MelonDebug.Msg($"[ALC FromStream] Validating {iAssemblyArrayLen}-byte assembly...");
+        //Continue to run the original runtime QCall.
+        return true;
+    }
 
-            byte[] assemblyBytes = new byte[iAssemblyArrayLen];
-            Marshal.Copy(ptrAssemblyArray, assemblyBytes, 0, iAssemblyArrayLen);
+    public static unsafe bool PreAlcLoadFromStream(IntPtr ptrAssemblyArray, int iAssemblyArrayLen)
+    {
+        //MelonDebug.Msg($"[ALC FromStream] Validating {iAssemblyArrayLen}-byte assembly...");
 
-            //Once again, pass to verifier and throw if bad.
-            var (ok, reason) = AssemblyVerifier.VerifyByteArray(assemblyBytes);
-            if (!ok)
-            {
-                throw new BadImageFormatException();
-            }
+        var assemblyBytes = new byte[iAssemblyArrayLen];
+        Marshal.Copy(ptrAssemblyArray, assemblyBytes, 0, iAssemblyArrayLen);
 
-            //And once again, continue to run the runtime QCall.
-            return true;
+        //Once again, pass to verifier and throw if bad.
+        var (ok, _) = AssemblyVerifier.VerifyByteArray(assemblyBytes);
+        if (!ok)
+        {
+            throw new BadImageFormatException();
         }
 
+        //And once again, continue to run the runtime QCall.
+        return true;
     }
 }
 #endif
\ No newline at end of file
diff --git a/MelonLoader/Fixes/DotnetModHandlerRedirectionFix.cs b/MelonLoader/Fixes/DotnetModHandlerRedirectionFix.cs
index 8cf01e47c..bd5f50699 100644
--- a/MelonLoader/Fixes/DotnetModHandlerRedirectionFix.cs
+++ b/MelonLoader/Fixes/DotnetModHandlerRedirectionFix.cs
@@ -4,30 +4,32 @@
 using System.Reflection;
 using System.Runtime.Loader;
 
-namespace MelonLoader.Fixes
+namespace MelonLoader.Fixes;
+
+internal class DotnetModHandlerRedirectionFix
 {
-    internal class DotnetModHandlerRedirectionFix
+    public static void Install()
     {
-        public static void Install()
+        try
         {
-            try
-            {
-                Core.HarmonyInstance.Patch(typeof(AssemblyLoadContext).GetMethod("ValidateAssemblyNameWithSimpleName", BindingFlags.Static | BindingFlags.NonPublic),
-                    new HarmonyMethod(typeof(DotnetModHandlerRedirectionFix), nameof(PreValidateAssembly)));
-            }
-            catch (Exception ex) { MelonLogger.Warning($"DotnetModHandlerRedirectionFix Exception: {ex}"); }
+            Core.HarmonyInstance.Patch(typeof(AssemblyLoadContext).GetMethod("ValidateAssemblyNameWithSimpleName", BindingFlags.Static | BindingFlags.NonPublic),
+                new HarmonyMethod(typeof(DotnetModHandlerRedirectionFix), nameof(PreValidateAssembly)));
         }
-
-        public static bool PreValidateAssembly(Assembly assembly, string requestedSimpleName, ref Assembly __result)
+        catch (Exception ex)
         {
-            if(requestedSimpleName.Contains("MelonLoader.ModHandler"))
-            {
-                __result = assembly;
-                return false; //Don't validate the name. What could go wrong?
-            }
+            MelonLogger.Warning($"DotnetModHandlerRedirectionFix Exception: {ex}");
+        }
+    }
 
-            return true;
+    public static bool PreValidateAssembly(Assembly assembly, string requestedSimpleName, ref Assembly __result)
+    {
+        if (requestedSimpleName.Contains("MelonLoader.ModHandler"))
+        {
+            __result = assembly;
+            return false; //Don't validate the name. What could go wrong?
         }
+
+        return true;
     }
 }
 
diff --git a/MelonLoader/Fixes/Il2CppICallInjector.cs b/MelonLoader/Fixes/Il2CppICallInjector.cs
index 7c4e00a4f..187e778d7 100644
--- a/MelonLoader/Fixes/Il2CppICallInjector.cs
+++ b/MelonLoader/Fixes/Il2CppICallInjector.cs
@@ -1,5 +1,7 @@
 #if NET6_0_OR_GREATER
 
+using HarmonyLib;
+using Il2CppInterop.HarmonySupport;
 using MelonLoader.NativeUtils;
 using MonoMod.RuntimeDetour;
 using MonoMod.Utils;
@@ -7,253 +9,254 @@
 using System.Collections.Generic;
 using System.Reflection;
 using System.Runtime.InteropServices;
-using Il2CppInterop.HarmonySupport;
-using HarmonyLib;
 
 #pragma warning disable 0649
 
-namespace MelonLoader.Fixes
+namespace MelonLoader.Fixes;
+
+internal static class Il2CppICallInjector
 {
-    internal static class Il2CppICallInjector
-    {
-        private static Dictionary _lookup = new();
+    private static Dictionary _lookup = [];
 
-        private delegate IntPtr dil2cpp_resolve_icall(IntPtr signature);
-        private static NativeHook il2cpp_resolve_icall_hook;
+    private delegate IntPtr dil2cpp_resolve_icall(IntPtr signature);
+    private static NativeHook il2cpp_resolve_icall_hook;
 
-        private delegate void dil2cpp_add_internal_call(IntPtr signature, IntPtr funcPtr);
-        private static dil2cpp_add_internal_call il2cpp_add_internal_call;
+    private delegate void dil2cpp_add_internal_call(IntPtr signature, IntPtr funcPtr);
+    private static dil2cpp_add_internal_call il2cpp_add_internal_call;
 
-        private static Type _il2CppDetourMethodPatcher;
-        private static MethodInfo _generateNativeToManagedTrampoline;
+    private static Type _il2CppDetourMethodPatcher;
+    private static MethodInfo _generateNativeToManagedTrampoline;
 
-        private static bool _extendedDebug;
-        private static MelonLogger.Instance _logger;
+    private static readonly bool _extendedDebug;
+    private static MelonLogger.Instance _logger;
 
-        internal static unsafe void Install()
+    internal static unsafe void Install()
+    {
+        try
         {
-            try
-            {
-                _logger = new MelonLogger.Instance(nameof(Il2CppICallInjector));
+            _logger = new MelonLogger.Instance(nameof(Il2CppICallInjector));
 
-                _il2CppDetourMethodPatcher = typeof(HarmonySupport).Assembly.GetType("Il2CppInterop.HarmonySupport.Il2CppDetourMethodPatcher");
-                if (_il2CppDetourMethodPatcher == null)
-                    throw new Exception("Failed to get Il2CppDetourMethodPatcher");
+            _il2CppDetourMethodPatcher = typeof(HarmonySupport).Assembly.GetType("Il2CppInterop.HarmonySupport.Il2CppDetourMethodPatcher");
+            if (_il2CppDetourMethodPatcher == null)
+                throw new Exception("Failed to get Il2CppDetourMethodPatcher");
 
-                _generateNativeToManagedTrampoline = _il2CppDetourMethodPatcher.GetMethod("GenerateNativeToManagedTrampoline", BindingFlags.NonPublic | BindingFlags.Instance);
-                if (_generateNativeToManagedTrampoline == null)
-                    throw new Exception("Failed to get Il2CppDetourMethodPatcher.GenerateNativeToManagedTrampoline");
+            _generateNativeToManagedTrampoline = _il2CppDetourMethodPatcher.GetMethod("GenerateNativeToManagedTrampoline", BindingFlags.NonPublic | BindingFlags.Instance);
+            if (_generateNativeToManagedTrampoline == null)
+                throw new Exception("Failed to get Il2CppDetourMethodPatcher.GenerateNativeToManagedTrampoline");
 
-                string gameAssemblyName = "GameAssembly";
-                NativeLibrary gameAssemblyLib = NativeLibrary.Load(gameAssemblyName);
-                if (gameAssemblyLib == null)
-                    throw new Exception($"Failed to load {gameAssemblyName} Native Library");
+            var gameAssemblyName = "GameAssembly";
+            var gameAssemblyLib = NativeLibrary.Load(gameAssemblyName);
+            if (gameAssemblyLib == null)
+                throw new Exception($"Failed to load {gameAssemblyName} Native Library");
 
-                IntPtr il2cpp_resolve_icall = gameAssemblyLib.GetExport(nameof(il2cpp_resolve_icall));
-                if (il2cpp_resolve_icall == IntPtr.Zero)
-                    throw new Exception($"Failed to get {nameof(il2cpp_resolve_icall)} Native Export");
+            IntPtr il2cpp_resolve_icall = gameAssemblyLib.GetExport(nameof(il2cpp_resolve_icall));
+            if (il2cpp_resolve_icall == IntPtr.Zero)
+                throw new Exception($"Failed to get {nameof(il2cpp_resolve_icall)} Native Export");
 
-                il2cpp_add_internal_call = gameAssemblyLib.GetExport(nameof(il2cpp_add_internal_call));
-                if (il2cpp_add_internal_call == null)
-                    throw new Exception($"Failed to get {nameof(il2cpp_add_internal_call)} Native Export");
+            il2cpp_add_internal_call = gameAssemblyLib.GetExport(nameof(il2cpp_add_internal_call));
+            if (il2cpp_add_internal_call == null)
+                throw new Exception($"Failed to get {nameof(il2cpp_add_internal_call)} Native Export");
 
-                MelonDebug.Msg("Patching il2cpp_resolve_icall...");
-                IntPtr detourPtr = Marshal.GetFunctionPointerForDelegate((dil2cpp_resolve_icall)il2cpp_resolve_icall_Detour);
-                il2cpp_resolve_icall_hook = new NativeHook(il2cpp_resolve_icall, detourPtr);
-                il2cpp_resolve_icall_hook.Attach();
-            }
-            catch (Exception e)
-            {
-                LogDebugWarning(e.ToString());
-            }
+            MelonDebug.Msg("Patching il2cpp_resolve_icall...");
+            var detourPtr = Marshal.GetFunctionPointerForDelegate((dil2cpp_resolve_icall)il2cpp_resolve_icall_Detour);
+            il2cpp_resolve_icall_hook = new NativeHook(il2cpp_resolve_icall, detourPtr);
+            il2cpp_resolve_icall_hook.Attach();
         }
+        catch (Exception e)
+        {
+            LogDebugWarning(e.ToString());
+        }
+    }
 
-        internal static void Shutdown()
+    internal static void Shutdown()
+    {
+        if (il2cpp_resolve_icall_hook != null)
         {
-            if (il2cpp_resolve_icall_hook != null)
-            {
-                if (il2cpp_resolve_icall_hook.IsHooked)
-                    il2cpp_resolve_icall_hook.Detach();
-                il2cpp_resolve_icall_hook = null;
-            }
+            if (il2cpp_resolve_icall_hook.IsHooked)
+                il2cpp_resolve_icall_hook.Detach();
+            il2cpp_resolve_icall_hook = null;
+        }
 
-            if (_lookup != null)
-            {
-                if (_lookup.Count > 0)
-                    _lookup.Clear();
-                _lookup = null;
-            }
+        if (_lookup != null)
+        {
+            if (_lookup.Count > 0)
+                _lookup.Clear();
+            _lookup = null;
         }
+    }
+
+    private static void LogMsg(string msg)
+        => _logger.Msg(msg);
+    private static void LogError(string msg)
+        => _logger.Error(msg);
+    private static void LogDebugMsg(string msg)
+    {
+        if (!_extendedDebug
+            || !MelonDebug.IsEnabled())
+            return;
+        _logger.Msg(msg);
+    }
+    private static void LogDebugWarning(string msg)
+    {
+        if (!_extendedDebug
+            || !MelonDebug.IsEnabled())
+            return;
+        _logger.Warning(msg);
+    }
 
-        private static void LogMsg(string msg)
-            => _logger.Msg(msg);
-        private static void LogError(string msg)
-            => _logger.Error(msg);
-        private static void LogDebugMsg(string msg)
+    private static IntPtr il2cpp_resolve_icall_Detour(IntPtr signature)
+    {
+        // Convert Pointer to String
+        var signatureStr = Marshal.PtrToStringAnsi(signature);
+        if (string.IsNullOrEmpty(signatureStr))
+            return IntPtr.Zero;
+
+        // Check Cache
+        if (_lookup.TryGetValue(signatureStr, out var result))
         {
-            if (!_extendedDebug
-                || !MelonDebug.IsEnabled())
-                return;
-            _logger.Msg(msg);
+            LogDebugMsg($"Resolved {signatureStr} to ICall in Cache");
+            return result.Item4;
         }
-        private static void LogDebugWarning(string msg)
+
+        // Run Original
+        var originalResult = il2cpp_resolve_icall_hook.Trampoline(signature);
+        if (originalResult != IntPtr.Zero)
         {
-            if (!_extendedDebug
-                || !MelonDebug.IsEnabled())
-                return;
-            _logger.Warning(msg);
+            // Cache Original Result
+            LogDebugMsg($"Resolved {signatureStr} to Unity ICall");
+            _lookup[signatureStr] = (null, null, null, originalResult);
+            return originalResult;
         }
 
-        private static IntPtr il2cpp_resolve_icall_Detour(IntPtr signature)
+        // Check if Injection is Needed
+        if (!ShouldInject(signatureStr, out var unityShimMethod))
         {
-            // Convert Pointer to String
-            string signatureStr = Marshal.PtrToStringAnsi(signature);
-            if (string.IsNullOrEmpty(signatureStr))
-                return IntPtr.Zero;
+            LogDebugWarning($"Unable to find suitable method to inject for {signatureStr}");
+            return IntPtr.Zero;
+        }
 
-            // Check Cache
-            if (_lookup.TryGetValue(signatureStr, out var result))
-            {
-                LogDebugMsg($"Resolved {signatureStr} to ICall in Cache");
-                return result.Item4;
-            }
+        // Create Injected Function and Cache Return
+        LogDebugMsg($"Generating Trampoline for {signatureStr}");
+        var pair = GenerateTrampoline(unityShimMethod);
+        if (pair.Item4 == IntPtr.Zero)
+        {
+            LogDebugWarning($"Failed to generate trampoline for {signatureStr}");
+            return IntPtr.Zero;
+        }
 
-            // Run Original
-            IntPtr originalResult = il2cpp_resolve_icall_hook.Trampoline(signature);
-            if (originalResult != IntPtr.Zero)
-            {
-                // Cache Original Result
-                LogDebugMsg($"Resolved {signatureStr} to Unity ICall");
-                _lookup[signatureStr] = (null, null, null, originalResult);
-                return originalResult;
-            }
+        // Add New ICall to Il2Cpp Domain
+        _lookup[signatureStr] = pair;
+        il2cpp_add_internal_call(signature, pair.Item4);
+        LogMsg($"Registered mono icall {signatureStr} in il2cpp domain");
 
-            // Check if Injection is Needed
-            if (!ShouldInject(signatureStr, out MethodInfo unityShimMethod))
-            {
-                LogDebugWarning($"Unable to find suitable method to inject for {signatureStr}");
-                return IntPtr.Zero;
-            }
+        // Return New Function Pointer
+        return pair.Item4;
+    }
 
-            // Create Injected Function and Cache Return
-            LogDebugMsg($"Generating Trampoline for {signatureStr}");
-            var pair = GenerateTrampoline(unityShimMethod);
-            if (pair.Item4 == IntPtr.Zero)
-            {
-                LogDebugWarning($"Failed to generate trampoline for {signatureStr}");
-                return IntPtr.Zero;
-            }
+    private static Type FindType(string typeFullName)
+    {
+        if (string.IsNullOrEmpty(typeFullName))
+            return null;
 
-            // Add New ICall to Il2Cpp Domain
-            _lookup[signatureStr] = pair;
-            il2cpp_add_internal_call(signature, pair.Item4);
-            LogMsg($"Registered mono icall {signatureStr} in il2cpp domain");
+        Type result = null;
+        foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
+        {
+            if (a == null)
+                continue;
 
-            // Return New Function Pointer
-            return pair.Item4;
-        }
+            result = a.GetValidType($"Il2Cpp.{typeFullName}");
+            if (result == null)
+                result = a.GetValidType($"Il2Cpp{typeFullName}");
+            if (result == null)
+                result = a.GetValidType(typeFullName);
 
-        private static Type FindType(string typeFullName)
-        {
-            if (string.IsNullOrEmpty(typeFullName))
-                return null;
+            if (result != null)
+                break;
+        }
 
-            Type result = null;
-            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
-            {
-                if (a == null)
-                    continue;
+        return result;
+    }
 
-                result = a.GetValidType($"Il2Cpp.{typeFullName}");
-                if (result == null)
-                    result = a.GetValidType($"Il2Cpp{typeFullName}");
-                if (result == null)
-                    result = a.GetValidType(typeFullName);
+    private static bool ShouldInject(string signature, out MethodInfo unityShimMethod)
+    {
+        unityShimMethod = null;
 
-                if (result != null)
-                    break;
-            }
+        // Split the Signature
+        var split = signature.Split("::");
+        var typeName = split[0];
+        var methodName = split[1];
 
-            return result;
-        }
+        // Find Managed Type
+        var newType = FindType(typeName);
+        if (newType == null)
+            return false;
 
-        private static bool ShouldInject(string signature, out MethodInfo unityShimMethod)
+        // Find Managed Method
+        MethodInfo targetMethod = null;
+        try
         {
-            unityShimMethod = null;
+            // Get All Methods
+            var allMethods = newType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
+            foreach (var method in allMethods)
+            {
+                // Validate Method
+                if ((method == null)
+                    || (method.Name != methodName))
+                    continue;
 
-            // Split the Signature
-            string[] split = signature.Split("::");
-            string typeName = split[0];
-            string methodName = split[1];
+                // Check for Generic Method since ICalls can't be Generic
+                if (method.IsGenericMethod)
+                    continue;
 
-            // Find Managed Type
-            Type newType = FindType(typeName);
-            if (newType == null)
-                return false;
+                // Check for PInvoke to prevent Recursion
+                if (method.Attributes.HasFlag(MethodAttributes.PinvokeImpl))
+                    continue;
 
-            // Find Managed Method
-            MethodInfo targetMethod = null;
-            try
-            {
-                // Get All Methods
-                MethodInfo[] allMethods = newType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
-                foreach (var method in allMethods)
-                {
-                    // Validate Method
-                    if ((method == null)
-                        || (method.Name != methodName))
-                        continue;
-
-                    // Check for Generic Method since ICalls can't be Generic
-                    if (method.IsGenericMethod)
-                        continue;
-
-                    // Check for PInvoke to prevent Recursion
-                    if (method.Attributes.HasFlag(MethodAttributes.PinvokeImpl))
-                        continue;
-
-                    // Check for Extern to prevent Recursion
-                    var methodImpl = method.GetMethodImplementationFlags();
-                    if (methodImpl.HasFlag(MethodImplAttributes.InternalCall)
-                        || methodImpl.HasFlag(MethodImplAttributes.Native)
-                        || methodImpl.HasFlag(MethodImplAttributes.Unmanaged))
-                        continue;
-
-                    // Check if Method has no Body or just throws NotImplementedException
-                    if (!method.HasMethodBody()
-                        || method.IsNotImplemented())
-                        continue;
-
-                    // Found Shim
-                    targetMethod = method;
-                    break;
-                }
-            }
-            catch { return false; }
-            if (targetMethod == null)
-                return false;
+                // Check for Extern to prevent Recursion
+                var methodImpl = method.GetMethodImplementationFlags();
+                if (methodImpl.HasFlag(MethodImplAttributes.InternalCall)
+                    || methodImpl.HasFlag(MethodImplAttributes.Native)
+                    || methodImpl.HasFlag(MethodImplAttributes.Unmanaged))
+                    continue;
 
-            // Inject ICall
-            unityShimMethod = targetMethod;
-            return true;
-        }
+                // Check if Method has no Body or just throws NotImplementedException
+                if (!method.HasMethodBody()
+                    || method.IsNotImplemented())
+                    continue;
 
-        private static (object, DynamicMethodDefinition, MethodInfo, IntPtr) GenerateTrampoline(MethodInfo unityShimMethod)
+                // Found Shim
+                targetMethod = method;
+                break;
+            }
+        }
+        catch
         {
-            // Create Patcher
-            object patcher = Activator.CreateInstance(_il2CppDetourMethodPatcher, [ unityShimMethod ]);
-            if (patcher == null)
-                return (null, null, null, IntPtr.Zero);
-
-            // Create New Injected ICall Method
-            DynamicMethodDefinition trampoline = (DynamicMethodDefinition)_generateNativeToManagedTrampoline.Invoke(patcher, [ unityShimMethod ]);
-            if (trampoline == null)
-                return (null, null, null, IntPtr.Zero);
-           
-            // Return the New Method
-            MethodInfo newMethod = trampoline.Generate().Pin();
-            return (patcher, trampoline, newMethod, newMethod.GetNativeStart());
+            return false;
         }
+
+        if (targetMethod == null)
+            return false;
+
+        // Inject ICall
+        unityShimMethod = targetMethod;
+        return true;
+    }
+
+    private static (object, DynamicMethodDefinition, MethodInfo, IntPtr) GenerateTrampoline(MethodInfo unityShimMethod)
+    {
+        // Create Patcher
+        var patcher = Activator.CreateInstance(_il2CppDetourMethodPatcher, [unityShimMethod]);
+        if (patcher == null)
+            return (null, null, null, IntPtr.Zero);
+
+        // Create New Injected ICall Method
+        var trampoline = (DynamicMethodDefinition)_generateNativeToManagedTrampoline.Invoke(patcher, [unityShimMethod]);
+        if (trampoline == null)
+            return (null, null, null, IntPtr.Zero);
+
+        // Return the New Method
+        var newMethod = trampoline.Generate().Pin();
+        return (patcher, trampoline, newMethod, newMethod.GetNativeStart());
     }
 }
 
diff --git a/MelonLoader/Fixes/Il2CppInteropFixes.cs b/MelonLoader/Fixes/Il2CppInteropFixes.cs
index c94fc0351..12907b14f 100644
--- a/MelonLoader/Fixes/Il2CppInteropFixes.cs
+++ b/MelonLoader/Fixes/Il2CppInteropFixes.cs
@@ -1,647 +1,643 @@
 #if NET6_0_OR_GREATER
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Reflection.Emit;
-using System.Runtime.InteropServices;
+using AsmResolver.DotNet;
+using HarmonyLib;
+using Il2CppInterop.Generator.Contexts;
 using Il2CppInterop.Generator.Extensions;
+using Il2CppInterop.HarmonySupport;
 using Il2CppInterop.Runtime;
 using Il2CppInterop.Runtime.Injection;
 using Il2CppInterop.Runtime.Runtime;
 using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class;
 using Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo;
 using Il2CppInterop.Runtime.Runtime.VersionSpecific.Type;
-using HarmonyLib;
-using System.IO;
 using MelonLoader.Utils;
-using Il2CppInterop.Generator.Contexts;
-using AsmResolver.DotNet;
-using Il2CppInterop.HarmonySupport;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
 
 #pragma warning disable CS8632
 
-namespace MelonLoader.Fixes
+namespace MelonLoader.Fixes;
+
+// fixes: https://github.com/BepInEx/Il2CppInterop/pull/103
+// fixes: https://github.com/BepInEx/Il2CppInterop/issues/135
+// reverts: https://github.com/BepInEx/Il2CppInterop/commit/18e58ef5db42a71d6012ab0387b107a4132101eb
+// fixes the rest of: https://github.com/BepInEx/Il2CppInterop/pull/134
+internal static unsafe class Il2CppInteropFixes
 {
-    // fixes: https://github.com/BepInEx/Il2CppInterop/pull/103
-    // fixes: https://github.com/BepInEx/Il2CppInterop/issues/135
-    // reverts: https://github.com/BepInEx/Il2CppInterop/commit/18e58ef5db42a71d6012ab0387b107a4132101eb
-    // fixes the rest of: https://github.com/BepInEx/Il2CppInterop/pull/134
-    internal unsafe static class Il2CppInteropFixes
+    private static readonly MelonLogger.Instance _logger = new("Il2CppInterop");
+
+    private static Dictionary> _assemblyLookup = [];
+    private static Dictionary _typeLookup = [];
+    private static readonly Dictionary _typeNameLookup = [];
+
+    private static MethodInfo _getType;
+    private static MethodInfo _fixedFindType;
+    private static MethodInfo _fixedAddTypeToLookup;
+    private static MethodInfo _rewriteType;
+    private static MethodInfo _rewriteType_Prefix;
+    private static MethodInfo _systemTypeFromIl2CppType;
+    private static MethodInfo _systemTypeFromIl2CppType_Prefix;
+    private static MethodInfo _systemTypeFromIl2CppType_Transpiler;
+    private static MethodInfo _injectorHelpers_AddTypeToLookup;
+    private static MethodInfo _registerTypeInIl2Cpp;
+    private static MethodInfo _registerTypeInIl2Cpp_Transpiler;
+    private static MethodInfo _isTypeSupported;
+    private static MethodInfo _isTypeSupported_Transpiler;
+    private static MethodInfo _convertMethodInfo;
+    private static MethodInfo _convertMethodInfo_Transpiler;
+    private static MethodInfo _getIl2CppTypeFullName;
+    private static MethodInfo _fixedIsByRef;
+    private static MethodInfo _get_IsByRef;
+    private static MethodInfo _fixedFindAbstractMethods;
+    private static MethodInfo _emitObjectToPointer;
+    private static MethodInfo _emitObjectToPointer_Prefix;
+    private static MethodInfo _rewriteGlobalContext_AddAssemblyContext;
+    private static MethodInfo _rewriteGlobalContext_AddAssemblyContext_Postfix;
+    private static MethodInfo _rewriteGlobalContext_Dispose;
+    private static MethodInfo _rewriteGlobalContext_Dispose_Prefix;
+    private static MethodInfo _rewriteGlobalContext_GetNewAssemblyForOriginal;
+    private static MethodInfo _rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix;
+    private static MethodInfo _rewriteGlobalContext_TryGetNewTypeForOriginal;
+    private static MethodInfo _rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix;
+    private static MethodInfo _reportException;
+    private static MethodInfo _reportException_Prefix;
+
+    private static void LogMsg(string msg)
+        => _logger.Msg(msg);
+    private static void LogError(Exception ex)
+        => _logger.Error(ex);
+    private static void LogError(string msg, Exception ex)
+        => _logger.Error(msg, ex);
+    private static void LogDebugMsg(string msg)
+    {
+        if (!MelonDebug.IsEnabled())
+            return;
+        _logger.Msg(msg);
+    }
+
+    internal static void Install()
     {
-        private static MelonLogger.Instance _logger = new("Il2CppInterop");
-
-        private static Dictionary> _assemblyLookup = new();
-        private static Dictionary _typeLookup = new();
-        private static Dictionary _typeNameLookup = new();
-
-        private static MethodInfo _getType;
-        private static MethodInfo _fixedFindType;
-        private static MethodInfo _fixedAddTypeToLookup;
-        private static MethodInfo _rewriteType;
-        private static MethodInfo _rewriteType_Prefix;
-        private static MethodInfo _systemTypeFromIl2CppType;
-        private static MethodInfo _systemTypeFromIl2CppType_Prefix;
-        private static MethodInfo _systemTypeFromIl2CppType_Transpiler;
-        private static MethodInfo _injectorHelpers_AddTypeToLookup;
-        private static MethodInfo _registerTypeInIl2Cpp;
-        private static MethodInfo _registerTypeInIl2Cpp_Transpiler;
-        private static MethodInfo _isTypeSupported;
-        private static MethodInfo _isTypeSupported_Transpiler;
-        private static MethodInfo _convertMethodInfo;
-        private static MethodInfo _convertMethodInfo_Transpiler;
-        private static MethodInfo _getIl2CppTypeFullName;
-        private static MethodInfo _fixedIsByRef;
-        private static MethodInfo _get_IsByRef;
-        private static MethodInfo _fixedFindAbstractMethods;
-        private static MethodInfo _emitObjectToPointer;
-        private static MethodInfo _emitObjectToPointer_Prefix;
-        private static MethodInfo _rewriteGlobalContext_AddAssemblyContext;
-        private static MethodInfo _rewriteGlobalContext_AddAssemblyContext_Postfix;
-        private static MethodInfo _rewriteGlobalContext_Dispose;
-        private static MethodInfo _rewriteGlobalContext_Dispose_Prefix;
-        private static MethodInfo _rewriteGlobalContext_GetNewAssemblyForOriginal;
-        private static MethodInfo _rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix;
-        private static MethodInfo _rewriteGlobalContext_TryGetNewTypeForOriginal;
-        private static MethodInfo _rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix;
-        private static MethodInfo _reportException;
-        private static MethodInfo _reportException_Prefix;
-
-        private static void LogMsg(string msg)
-            => _logger.Msg(msg);
-        private static void LogError(Exception ex)
-            => _logger.Error(ex);
-        private static void LogError(string msg, Exception ex)
-            => _logger.Error(msg, ex);
-        private static void LogDebugMsg(string msg)
+        try
         {
-            if (!MelonDebug.IsEnabled())
-                return;
-            _logger.Msg(msg);
+            var typeType = typeof(Type);
+            var thisType = typeof(Il2CppInteropFixes);
+            var classInjectorType = typeof(ClassInjector);
+            var ilGeneratorEx = typeof(ILGeneratorEx);
+            var rewriteGlobalContextType = typeof(RewriteGlobalContext);
+            var il2cppType = typeof(IL2CPP);
+            var harmonySupportType = typeof(HarmonySupport);
+
+            var injectorHelpersType = classInjectorType.Assembly.GetType("Il2CppInterop.Runtime.Injection.InjectorHelpers");
+            if (injectorHelpersType == null)
+                throw new Exception("Failed to get InjectorHelpers");
+
+            var detourMethodPatcherType = harmonySupportType.Assembly.GetType("Il2CppInterop.HarmonySupport.Il2CppDetourMethodPatcher");
+            if (detourMethodPatcherType == null)
+                throw new Exception("Failed to get Il2CppDetourMethodPatcher");
+
+            _systemTypeFromIl2CppType = classInjectorType.GetMethod("SystemTypeFromIl2CppType", BindingFlags.NonPublic | BindingFlags.Static);
+            if (_systemTypeFromIl2CppType == null)
+                throw new Exception("Failed to get ClassInjector.SystemTypeFromIl2CppType");
+
+            _getIl2CppTypeFullName = classInjectorType.GetMethod("GetIl2CppTypeFullName", BindingFlags.NonPublic | BindingFlags.Static);
+            if (_getIl2CppTypeFullName == null)
+                throw new Exception("Failed to get ClassInjector.GetIl2CppTypeFullName");
+
+            _rewriteType = classInjectorType.GetMethod("RewriteType", BindingFlags.NonPublic | BindingFlags.Static);
+            if (_rewriteType == null)
+                throw new Exception("Failed to get ClassInjector.RewriteType");
+
+            _registerTypeInIl2Cpp = classInjectorType.GetMethod("RegisterTypeInIl2Cpp",
+                BindingFlags.Public | BindingFlags.Static,
+                [typeof(Type), typeof(RegisterTypeOptions)]);
+            if (_registerTypeInIl2Cpp == null)
+                throw new Exception("Failed to get ClassInjector.RegisterTypeInIl2Cpp");
+
+            _isTypeSupported = classInjectorType.GetMethod("IsTypeSupported", BindingFlags.NonPublic | BindingFlags.Static);
+            if (_isTypeSupported == null)
+                throw new Exception("Failed to get ClassInjector.IsTypeSupported");
+
+            _convertMethodInfo = classInjectorType.GetMethod("ConvertMethodInfo", BindingFlags.NonPublic | BindingFlags.Static);
+            if (_convertMethodInfo == null)
+                throw new Exception("Failed to get ClassInjector.ConvertMethodInfo");
+
+            _emitObjectToPointer = ilGeneratorEx.GetMethod("EmitObjectToPointer", BindingFlags.Public | BindingFlags.Static);
+            if (_emitObjectToPointer == null)
+                throw new Exception("Failed to get ILGeneratorEx.EmitObjectToPointer");
+
+            _injectorHelpers_AddTypeToLookup = injectorHelpersType.GetMethod("AddTypeToLookup",
+                BindingFlags.NonPublic | BindingFlags.Static,
+                [typeof(Type), typeof(IntPtr)]);
+            if (_injectorHelpers_AddTypeToLookup == null)
+                throw new Exception("Failed to get InjectorHelpers.AddTypeToLookup");
+
+            _getType = typeType.GetMethod("GetType", BindingFlags.Public | BindingFlags.Static, [typeof(string)]);
+            if (_getType == null)
+                throw new Exception("Failed to get Type.GetType");
+
+            _get_IsByRef = typeType.GetProperty("IsByRef", BindingFlags.Public | BindingFlags.Instance)?.GetGetMethod();
+            if (_get_IsByRef == null)
+                throw new Exception("Failed to get Type.IsByRef.get");
+
+            _rewriteGlobalContext_AddAssemblyContext = rewriteGlobalContextType.GetMethod("AddAssemblyContext",
+                BindingFlags.NonPublic | BindingFlags.Instance);
+            if (_rewriteGlobalContext_AddAssemblyContext == null)
+                throw new Exception("Failed to get RewriteGlobalContext.AddAssemblyContext");
+
+            _rewriteGlobalContext_Dispose = rewriteGlobalContextType.GetMethod("Dispose",
+                BindingFlags.Public | BindingFlags.Instance);
+            if (_rewriteGlobalContext_Dispose == null)
+                throw new Exception("Failed to get RewriteGlobalContext.Dispose");
+
+            _rewriteGlobalContext_GetNewAssemblyForOriginal = rewriteGlobalContextType.GetMethod("GetNewAssemblyForOriginal",
+                BindingFlags.Public | BindingFlags.Instance);
+            if (_rewriteGlobalContext_GetNewAssemblyForOriginal == null)
+                throw new Exception("Failed to get RewriteGlobalContext.GetNewAssemblyForOriginal");
+
+            _rewriteGlobalContext_TryGetNewTypeForOriginal = rewriteGlobalContextType.GetMethod("TryGetNewTypeForOriginal",
+                BindingFlags.Public | BindingFlags.Instance);
+            if (_rewriteGlobalContext_TryGetNewTypeForOriginal == null)
+                throw new Exception("Failed to get RewriteGlobalContext.TryGetNewTypeForOriginal");
+
+            _reportException = detourMethodPatcherType.GetMethod("ReportException",
+                BindingFlags.NonPublic | BindingFlags.Static);
+            if (_reportException == null)
+                throw new Exception("Failed to get Il2CppDetourMethodPatcher.ReportException");
+
+            _fixedFindType = thisType.GetMethod(nameof(FixedFindType), BindingFlags.NonPublic | BindingFlags.Static);
+            _fixedAddTypeToLookup = thisType.GetMethod(nameof(FixedAddTypeToLookup), BindingFlags.NonPublic | BindingFlags.Static);
+            _fixedIsByRef = thisType.GetMethod(nameof(FixedIsByRef), BindingFlags.NonPublic | BindingFlags.Static);
+            _fixedFindAbstractMethods = thisType.GetMethod(nameof(FixedFindAbstractMethods), BindingFlags.NonPublic | BindingFlags.Static);
+            _systemTypeFromIl2CppType_Prefix = thisType.GetMethod(nameof(SystemTypeFromIl2CppType_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
+            _systemTypeFromIl2CppType_Transpiler = thisType.GetMethod(nameof(SystemTypeFromIl2CppType_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
+            _rewriteType_Prefix = thisType.GetMethod(nameof(RewriteType_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
+            _registerTypeInIl2Cpp_Transpiler = thisType.GetMethod(nameof(RegisterTypeInIl2Cpp_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
+            _isTypeSupported_Transpiler = thisType.GetMethod(nameof(IsTypeSupported_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
+            _convertMethodInfo_Transpiler = thisType.GetMethod(nameof(ConvertMethodInfo_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
+            _emitObjectToPointer_Prefix = thisType.GetMethod(nameof(EmitObjectToPointer_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
+            _rewriteGlobalContext_AddAssemblyContext_Postfix = thisType.GetMethod(nameof(RewriteGlobalContext_AddAssemblyContext_Postfix), BindingFlags.NonPublic | BindingFlags.Static);
+            _rewriteGlobalContext_Dispose_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_Dispose_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
+            _rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_GetNewAssemblyForOriginal_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
+            _rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
+            _reportException_Prefix = thisType.GetMethod(nameof(ReportException_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
+
+            LogDebugMsg("Patching Il2CppInterop ClassInjector.SystemTypeFromIl2CppType...");
+            Core.HarmonyInstance.Patch(_systemTypeFromIl2CppType,
+                new HarmonyMethod(_systemTypeFromIl2CppType_Prefix),
+                null,
+                new HarmonyMethod(_systemTypeFromIl2CppType_Transpiler));
+
+            LogDebugMsg("Patching Il2CppInterop ClassInjector.RegisterTypeInIl2Cpp...");
+            Core.HarmonyInstance.Patch(_registerTypeInIl2Cpp,
+                null,
+                null,
+                new HarmonyMethod(_registerTypeInIl2Cpp_Transpiler));
+
+            LogDebugMsg("Patching Il2CppInterop ClassInjector.IsTypeSupported...");
+            Core.HarmonyInstance.Patch(_isTypeSupported,
+                null,
+                null,
+                new HarmonyMethod(_isTypeSupported_Transpiler));
+
+            LogDebugMsg("Patching Il2CppInterop ClassInjector.RewriteType...");
+            Core.HarmonyInstance.Patch(_rewriteType,
+                new HarmonyMethod(_rewriteType_Prefix));
+
+            LogDebugMsg("Patching Il2CppInterop ClassInjector.ConvertMethodInfo...");
+            Core.HarmonyInstance.Patch(_convertMethodInfo,
+                null,
+                null,
+                new HarmonyMethod(_convertMethodInfo_Transpiler));
+
+            LogDebugMsg("Patching Il2CppInterop ILGeneratorEx.EmitObjectToPointer...");
+            Core.HarmonyInstance.Patch(_emitObjectToPointer,
+                new HarmonyMethod(_emitObjectToPointer_Prefix));
+
+            LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.AddAssemblyContext...");
+            Core.HarmonyInstance.Patch(_rewriteGlobalContext_AddAssemblyContext,
+                null, new HarmonyMethod(_rewriteGlobalContext_AddAssemblyContext_Postfix));
+
+            LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.Dispose...");
+            Core.HarmonyInstance.Patch(_rewriteGlobalContext_Dispose,
+                new HarmonyMethod(_rewriteGlobalContext_Dispose_Prefix));
+
+            LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.GetNewAssemblyForOriginal...");
+            Core.HarmonyInstance.Patch(_rewriteGlobalContext_GetNewAssemblyForOriginal,
+                new HarmonyMethod(_rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix));
+
+            LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.TryGetNewTypeForOriginal...");
+            Core.HarmonyInstance.Patch(_rewriteGlobalContext_TryGetNewTypeForOriginal,
+                new HarmonyMethod(_rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix));
+
+            LogDebugMsg("Patching Il2CppInterop Il2CppDetourMethodPatcher.ReportException...");
+            Core.HarmonyInstance.Patch(_reportException,
+                new HarmonyMethod(_reportException_Prefix));
         }
-
-        internal static void Install()
+        catch (Exception e)
         {
-            try
-            {
-                Type typeType = typeof(Type);
-                Type thisType = typeof(Il2CppInteropFixes);
-                Type classInjectorType = typeof(ClassInjector);
-                Type ilGeneratorEx = typeof(ILGeneratorEx);
-                Type rewriteGlobalContextType = typeof(RewriteGlobalContext);
-                Type il2cppType = typeof(IL2CPP);
-                Type harmonySupportType = typeof(HarmonySupport);
-
-                Type injectorHelpersType = classInjectorType.Assembly.GetType("Il2CppInterop.Runtime.Injection.InjectorHelpers");
-                if (injectorHelpersType == null)
-                    throw new Exception("Failed to get InjectorHelpers");
-
-                Type detourMethodPatcherType = harmonySupportType.Assembly.GetType("Il2CppInterop.HarmonySupport.Il2CppDetourMethodPatcher");
-                if (detourMethodPatcherType == null)
-                    throw new Exception("Failed to get Il2CppDetourMethodPatcher");
-
-                _systemTypeFromIl2CppType = classInjectorType.GetMethod("SystemTypeFromIl2CppType", BindingFlags.NonPublic | BindingFlags.Static);
-                if (_systemTypeFromIl2CppType == null)
-                    throw new Exception("Failed to get ClassInjector.SystemTypeFromIl2CppType");
-
-                _getIl2CppTypeFullName = classInjectorType.GetMethod("GetIl2CppTypeFullName", BindingFlags.NonPublic | BindingFlags.Static);
-                if (_getIl2CppTypeFullName == null)
-                    throw new Exception("Failed to get ClassInjector.GetIl2CppTypeFullName");
-
-                _rewriteType = classInjectorType.GetMethod("RewriteType", BindingFlags.NonPublic | BindingFlags.Static);
-                if (_rewriteType == null)
-                    throw new Exception("Failed to get ClassInjector.RewriteType");
-
-                _registerTypeInIl2Cpp = classInjectorType.GetMethod("RegisterTypeInIl2Cpp",
-                    BindingFlags.Public | BindingFlags.Static, 
-                    [typeof(Type), typeof(RegisterTypeOptions)]);
-                if (_registerTypeInIl2Cpp == null)
-                    throw new Exception("Failed to get ClassInjector.RegisterTypeInIl2Cpp");
-
-                _isTypeSupported = classInjectorType.GetMethod("IsTypeSupported", BindingFlags.NonPublic | BindingFlags.Static);
-                if (_isTypeSupported == null)
-                    throw new Exception("Failed to get ClassInjector.IsTypeSupported");
-
-                _convertMethodInfo = classInjectorType.GetMethod("ConvertMethodInfo", BindingFlags.NonPublic | BindingFlags.Static);
-                if (_convertMethodInfo == null)
-                    throw new Exception("Failed to get ClassInjector.ConvertMethodInfo");
-
-                _emitObjectToPointer = ilGeneratorEx.GetMethod("EmitObjectToPointer", BindingFlags.Public | BindingFlags.Static);
-                if (_emitObjectToPointer == null)
-                    throw new Exception("Failed to get ILGeneratorEx.EmitObjectToPointer");
-
-                _injectorHelpers_AddTypeToLookup = injectorHelpersType.GetMethod("AddTypeToLookup", 
-                    BindingFlags.NonPublic | BindingFlags.Static, 
-                    [typeof(Type), typeof(IntPtr)]);
-                if (_injectorHelpers_AddTypeToLookup == null)
-                    throw new Exception("Failed to get InjectorHelpers.AddTypeToLookup");
-
-                _getType = typeType.GetMethod("GetType", BindingFlags.Public | BindingFlags.Static, [typeof(string)]);
-                if (_getType == null)
-                    throw new Exception("Failed to get Type.GetType");
-
-                _get_IsByRef = typeType.GetProperty("IsByRef", BindingFlags.Public | BindingFlags.Instance)?.GetGetMethod();
-                if (_get_IsByRef == null)
-                    throw new Exception("Failed to get Type.IsByRef.get");
-
-                _rewriteGlobalContext_AddAssemblyContext = rewriteGlobalContextType.GetMethod("AddAssemblyContext", 
-                    BindingFlags.NonPublic | BindingFlags.Instance);
-                if (_rewriteGlobalContext_AddAssemblyContext == null)
-                    throw new Exception("Failed to get RewriteGlobalContext.AddAssemblyContext");
-
-                _rewriteGlobalContext_Dispose = rewriteGlobalContextType.GetMethod("Dispose",
-                    BindingFlags.Public | BindingFlags.Instance);
-                if (_rewriteGlobalContext_Dispose == null)
-                    throw new Exception("Failed to get RewriteGlobalContext.Dispose");
-
-                _rewriteGlobalContext_GetNewAssemblyForOriginal = rewriteGlobalContextType.GetMethod("GetNewAssemblyForOriginal",
-                    BindingFlags.Public | BindingFlags.Instance);
-                if (_rewriteGlobalContext_GetNewAssemblyForOriginal == null)
-                    throw new Exception("Failed to get RewriteGlobalContext.GetNewAssemblyForOriginal");
-
-                _rewriteGlobalContext_TryGetNewTypeForOriginal = rewriteGlobalContextType.GetMethod("TryGetNewTypeForOriginal",
-                    BindingFlags.Public | BindingFlags.Instance);
-                if (_rewriteGlobalContext_TryGetNewTypeForOriginal == null)
-                    throw new Exception("Failed to get RewriteGlobalContext.TryGetNewTypeForOriginal");
-
-                _reportException = detourMethodPatcherType.GetMethod("ReportException",
-                    BindingFlags.NonPublic | BindingFlags.Static);
-                if (_reportException == null)
-                    throw new Exception("Failed to get Il2CppDetourMethodPatcher.ReportException");
-
-                _fixedFindType = thisType.GetMethod(nameof(FixedFindType), BindingFlags.NonPublic | BindingFlags.Static);
-                _fixedAddTypeToLookup = thisType.GetMethod(nameof(FixedAddTypeToLookup), BindingFlags.NonPublic | BindingFlags.Static);
-                _fixedIsByRef = thisType.GetMethod(nameof(FixedIsByRef), BindingFlags.NonPublic | BindingFlags.Static);
-                _fixedFindAbstractMethods = thisType.GetMethod(nameof(FixedFindAbstractMethods), BindingFlags.NonPublic | BindingFlags.Static);
-                _systemTypeFromIl2CppType_Prefix = thisType.GetMethod(nameof(SystemTypeFromIl2CppType_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
-                _systemTypeFromIl2CppType_Transpiler = thisType.GetMethod(nameof(SystemTypeFromIl2CppType_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
-                _rewriteType_Prefix = thisType.GetMethod(nameof(RewriteType_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
-                _registerTypeInIl2Cpp_Transpiler = thisType.GetMethod(nameof(RegisterTypeInIl2Cpp_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
-                _isTypeSupported_Transpiler = thisType.GetMethod(nameof(IsTypeSupported_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
-                _convertMethodInfo_Transpiler = thisType.GetMethod(nameof(ConvertMethodInfo_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
-                _emitObjectToPointer_Prefix = thisType.GetMethod(nameof(EmitObjectToPointer_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
-                _rewriteGlobalContext_AddAssemblyContext_Postfix = thisType.GetMethod(nameof(RewriteGlobalContext_AddAssemblyContext_Postfix), BindingFlags.NonPublic | BindingFlags.Static);
-                _rewriteGlobalContext_Dispose_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_Dispose_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
-                _rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_GetNewAssemblyForOriginal_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
-                _rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix = thisType.GetMethod(nameof(RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
-                _reportException_Prefix = thisType.GetMethod(nameof(ReportException_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
-
-                LogDebugMsg("Patching Il2CppInterop ClassInjector.SystemTypeFromIl2CppType...");
-                Core.HarmonyInstance.Patch(_systemTypeFromIl2CppType,
-                    new HarmonyMethod(_systemTypeFromIl2CppType_Prefix), 
-                    null,
-                    new HarmonyMethod(_systemTypeFromIl2CppType_Transpiler));
-
-                LogDebugMsg("Patching Il2CppInterop ClassInjector.RegisterTypeInIl2Cpp...");
-                Core.HarmonyInstance.Patch(_registerTypeInIl2Cpp,
-                    null,
-                    null,
-                    new HarmonyMethod(_registerTypeInIl2Cpp_Transpiler));
-
-                LogDebugMsg("Patching Il2CppInterop ClassInjector.IsTypeSupported...");
-                Core.HarmonyInstance.Patch(_isTypeSupported,
-                    null,
-                    null,
-                    new HarmonyMethod(_isTypeSupported_Transpiler));
-
-                LogDebugMsg("Patching Il2CppInterop ClassInjector.RewriteType...");
-                Core.HarmonyInstance.Patch(_rewriteType,
-                    new HarmonyMethod(_rewriteType_Prefix));
-
-                LogDebugMsg("Patching Il2CppInterop ClassInjector.ConvertMethodInfo...");
-                Core.HarmonyInstance.Patch(_convertMethodInfo,
-                    null,
-                    null,
-                    new HarmonyMethod(_convertMethodInfo_Transpiler));
-
-                LogDebugMsg("Patching Il2CppInterop ILGeneratorEx.EmitObjectToPointer...");
-                Core.HarmonyInstance.Patch(_emitObjectToPointer,
-                    new HarmonyMethod(_emitObjectToPointer_Prefix));
-
-                LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.AddAssemblyContext...");
-                Core.HarmonyInstance.Patch(_rewriteGlobalContext_AddAssemblyContext,
-                    null, new HarmonyMethod(_rewriteGlobalContext_AddAssemblyContext_Postfix));
-
-                LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.Dispose...");
-                Core.HarmonyInstance.Patch(_rewriteGlobalContext_Dispose,
-                    new HarmonyMethod(_rewriteGlobalContext_Dispose_Prefix));
-
-                LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.GetNewAssemblyForOriginal...");
-                Core.HarmonyInstance.Patch(_rewriteGlobalContext_GetNewAssemblyForOriginal,
-                    new HarmonyMethod(_rewriteGlobalContext_GetNewAssemblyForOriginal_Prefix));
-
-                LogDebugMsg("Patching Il2CppInterop RewriteGlobalContext.TryGetNewTypeForOriginal...");
-                Core.HarmonyInstance.Patch(_rewriteGlobalContext_TryGetNewTypeForOriginal,
-                    new HarmonyMethod(_rewriteGlobalContext_TryGetNewTypeForOriginal_Prefix));
-
-                LogDebugMsg("Patching Il2CppInterop Il2CppDetourMethodPatcher.ReportException...");
-                Core.HarmonyInstance.Patch(_reportException,
-                    new HarmonyMethod(_reportException_Prefix));
-            }
-            catch (Exception e)
-            {
-                LogError(e);
-            }
+            LogError(e);
         }
+    }
 
-        internal static void Shutdown()
+    internal static void Shutdown()
+    {
+        if (_assemblyLookup != null)
         {
-            if (_assemblyLookup != null)
+            if (_assemblyLookup.Count > 0)
             {
-                if (_assemblyLookup.Count > 0)
-                {
-                    foreach (Dictionary dict in _assemblyLookup.Values)
-                        dict.Clear();
-                    _assemblyLookup.Clear();
-                }
-                _assemblyLookup = null;
+                foreach (var dict in _assemblyLookup.Values)
+                    dict.Clear();
+                _assemblyLookup.Clear();
             }
 
-            if (_typeLookup != null)
-            {
-                if (_typeLookup.Count > 0)
-                    _typeLookup.Clear();
-                _typeLookup = null;
-            }
+            _assemblyLookup = null;
+        }
+
+        if (_typeLookup != null)
+        {
+            if (_typeLookup.Count > 0)
+                _typeLookup.Clear();
+            _typeLookup = null;
         }
+    }
+
+    private static bool FixedIsByRef(Type type)
+        => (type != null) && (type.IsByRef || type.IsPointer);
 
-        private static bool FixedIsByRef(Type type)
-            => (type != null) && (type.IsByRef || type.IsPointer);
+    internal static Type FixedFindType(string typeFullName)
+    {
+        if (string.IsNullOrEmpty(typeFullName))
+            return null;
 
-        internal static Type FixedFindType(string typeFullName)
+        if (_typeNameLookup.TryGetValue(typeFullName, out var result))
+            return result;
+
+        foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
         {
-            if (string.IsNullOrEmpty(typeFullName))
-                return null;
+            if (a == null)
+                continue;
 
-            if (_typeNameLookup.TryGetValue(typeFullName, out Type result))
-                return result;
+            result = a.GetValidType($"Il2Cpp.{typeFullName}");
+            if (result == null)
+                result = a.GetValidType($"Il2Cpp{typeFullName}");
+            if (result == null)
+                result = a.GetValidType(typeFullName);
 
-            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
+            if (result != null)
             {
-                if (a == null)
-                    continue;
+                _typeNameLookup[result.FullName] = result;
+                return result;
+            }
+        }
 
-                result = a.GetValidType($"Il2Cpp.{typeFullName}");
-                if (result == null)
-                    result = a.GetValidType($"Il2Cpp{typeFullName}");
-                if (result == null)
-                    result = a.GetValidType(typeFullName);
+        return null;
+    }
 
-                if (result != null)
-                {
-                    _typeNameLookup[result.FullName] = result;
-                    return result;
-                }
-            }
+    private static void FixedAddTypeToLookup(Type type, IntPtr typePointer)
+    {
+        if ((type == null)
+            || (typePointer == IntPtr.Zero))
+            return;
 
-            return null;
-        }
+        _injectorHelpers_AddTypeToLookup.Invoke(null, [type, typePointer]);
 
-        private static void FixedAddTypeToLookup(Type type, IntPtr typePointer)
-        {
-            if ((type == null)
-                || (typePointer == IntPtr.Zero))
-                return;
+        typePointer = IL2CPP.il2cpp_class_get_type(typePointer);
+        if (typePointer != IntPtr.Zero)
+            _typeLookup.Add(typePointer, type);
+    }
 
-            _injectorHelpers_AddTypeToLookup.Invoke(null, [type, typePointer]);
+    private static bool ReportException_Prefix(Exception __0)
+    {
+        LogError("During invoking native->managed trampoline", __0);
+        return false;
+    }
 
-            typePointer = IL2CPP.il2cpp_class_get_type(typePointer);
-            if (typePointer !=  IntPtr.Zero)
-                _typeLookup.Add(typePointer, type);
-        }
+    private static bool EmitObjectToPointer_Prefix(bool __7, ref bool __8)
+    {
+        __8 = __7;
+        return true;
+    }
+
+    private static bool RewriteType_Prefix(Type __0, ref Type __result)
+    {
+        if (__0 == null)
+            return true;
 
-        private static bool ReportException_Prefix(Exception __0)
+        if (__0 == typeof(void*))
         {
-            LogError("During invoking native->managed trampoline", __0);
+            __result = __0;
             return false;
         }
 
-        private static bool EmitObjectToPointer_Prefix(bool __7, ref bool __8)
-        {
-            __8 = __7;
+        return true;
+    }
+
+    private static void RewriteGlobalContext_AddAssemblyContext_Postfix(RewriteGlobalContext __instance,
+        AssemblyRewriteContext __1)
+    {
+        if ((__instance == null)
+            || (__1 == null)
+            || __1.OriginalAssembly == null)
+            return;
+
+        if (!_assemblyLookup.TryGetValue(__instance, out var contexts)
+            || (contexts == null))
+            contexts = _assemblyLookup[__instance] = [];
+
+        string assemblyName = __1.OriginalAssembly.Name;
+        if (string.IsNullOrEmpty(assemblyName))
+            return;
+
+        contexts[assemblyName] = __1;
+        //LogDebugMsg($"[RewriteGlobalContext] Added: {assemblyName}");
+    }
+
+    private static bool RewriteGlobalContext_Dispose_Prefix(RewriteGlobalContext __instance)
+    {
+        if ((__instance == null)
+            || !_assemblyLookup.ContainsKey(__instance)
+            || !_assemblyLookup.Remove(__instance, out var contexts)
+            || (contexts == null))
             return true;
+
+        contexts.Clear();
+        return true;
+    }
+
+    private static bool RewriteGlobalContext_GetNewAssemblyForOriginal_Prefix(RewriteGlobalContext __instance,
+        AssemblyDefinition __0,
+        ref AssemblyRewriteContext __result)
+    {
+        if ((__instance == null)
+            || (__0 == null)
+            || !_assemblyLookup.TryGetValue(__instance, out var contexts)
+            || (contexts == null))
+            return true;
+
+        string assemblyName = __0.Name;
+        if (contexts.TryGetValue(assemblyName, out __result))
+        {
+            //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
+            return false;
         }
 
-        private static bool RewriteType_Prefix(Type __0, ref Type __result)
+        assemblyName = assemblyName.StartsWith("Il2Cpp") ? assemblyName.Remove(0, 6) : $"Il2Cpp{assemblyName}";
+
+        if (contexts.TryGetValue(assemblyName, out __result))
         {
-            if (__0 == null)
-                return true;
+            //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
+            return false;
+        }
 
-            if (__0 == typeof(void*))
-            {
-                __result = __0;
-                return false;
-            }
+        return true;
+    }
 
+    private static bool RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix(RewriteGlobalContext __instance,
+        TypeDefinition __0,
+        ref TypeRewriteContext? __result)
+    {
+        if ((__instance == null)
+            || (__0 == null)
+            || (__0.Module == null)
+            || (__0.Module.Assembly == null)
+            || !_assemblyLookup.TryGetValue(__instance, out var contexts)
+            || (contexts == null))
             return true;
+
+        string assemblyName = __0.Module.Assembly.Name;
+        if (string.IsNullOrEmpty(assemblyName))
+            return false;
+
+        AssemblyRewriteContext rewriteContext;
+        if (contexts.TryGetValue(assemblyName, out rewriteContext))
+        {
+            //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
+            __result = rewriteContext.TryGetContextForOriginalType(__0);
+            return false;
         }
 
-        private static void RewriteGlobalContext_AddAssemblyContext_Postfix(RewriteGlobalContext __instance,
-            AssemblyRewriteContext __1)
+        assemblyName = assemblyName.StartsWith("Il2Cpp") ? assemblyName.Remove(0, 6) : $"Il2Cpp{assemblyName}";
+        if (contexts.TryGetValue(assemblyName, out rewriteContext))
         {
-            if ((__instance == null)
-                || (__1 == null)
-                || __1.OriginalAssembly == null)
-                return;
+            //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
+            __result = rewriteContext.TryGetContextForOriginalType(__0);
+            return false;
+        }
 
-            if (!_assemblyLookup.TryGetValue(__instance, out Dictionary contexts)
-                || (contexts == null))
-                contexts = _assemblyLookup[__instance] = new();
+        return true;
+    }
 
-            string assemblyName = __1.OriginalAssembly.Name;
-            if (string.IsNullOrEmpty(assemblyName))
-                return;
+    private static bool SystemTypeFromIl2CppType_Prefix(Il2CppTypeStruct* __0, ref Type __result)
+    {
+        if ((IntPtr)__0 == IntPtr.Zero)
+            return false;
 
-            contexts[assemblyName] = __1;
-            //LogDebugMsg($"[RewriteGlobalContext] Added: {assemblyName}");
-        }
+        var wrappedType = UnityVersionHandler.Wrap(__0);
+        if ((IntPtr)wrappedType.TypePointer == IntPtr.Zero)
+            return false;
 
-        private static bool RewriteGlobalContext_Dispose_Prefix(RewriteGlobalContext __instance)
+        if (_typeLookup.TryGetValue((IntPtr)wrappedType.TypePointer, out var type))
         {
-            if ((__instance == null)
-                || !_assemblyLookup.ContainsKey(__instance)
-                || !_assemblyLookup.Remove(__instance, out Dictionary contexts)
-                || (contexts == null))
-                return true;
+            __result = (Type)_rewriteType.Invoke(null, [type]);
+            return false;
+        }
 
-            contexts.Clear();
+        var klass = IL2CPP.il2cpp_class_from_type((IntPtr)wrappedType.TypePointer);
+        if (klass == IntPtr.Zero)
             return true;
-        }
 
-        private static bool RewriteGlobalContext_GetNewAssemblyForOriginal_Prefix(RewriteGlobalContext __instance,
-            AssemblyDefinition __0,
-            ref AssemblyRewriteContext __result)
-        {
-            if ((__instance == null)
-                || (__0 == null)
-                || !_assemblyLookup.TryGetValue(__instance, out Dictionary contexts)
-                || (contexts == null))
-                return true;
-
-            string assemblyName = __0.Name;
-            if (contexts.TryGetValue(assemblyName, out __result))
-            {
-                //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
-                return false;
-            }
+        var image = IL2CPP.il2cpp_class_get_image(klass);
+        if (image == IntPtr.Zero)
+            return true;
 
-            if (assemblyName.StartsWith("Il2Cpp"))
-                assemblyName = assemblyName.Remove(0, 6);
-            else
-                assemblyName = $"Il2Cpp{assemblyName}";
+        var klassNamespace = IL2CPP.il2cpp_class_get_namespace(klass);
+        if (klassNamespace == IntPtr.Zero)
+            return true;
 
-            if (contexts.TryGetValue(assemblyName, out __result))
-            {
-                //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
-                return false;
-            }
+        var klassName = IL2CPP.il2cpp_class_get_name(klass);
+        if (klassName == IntPtr.Zero)
+            return true;
 
+        var klassNameStr = Marshal.PtrToStringAnsi(klassName);
+        if (string.IsNullOrEmpty(klassNameStr))
             return true;
-        }
 
-        private static bool RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix(RewriteGlobalContext __instance,
-            TypeDefinition __0,
-            ref TypeRewriteContext? __result)
+        var fullTypeName = klassNameStr;
+        if (klassNamespace != IntPtr.Zero)
         {
-            if ((__instance == null)
-                || (__0 == null)
-                || (__0.Module == null)
-                || (__0.Module.Assembly == null)
-                || !_assemblyLookup.TryGetValue(__instance, out Dictionary contexts)
-                || (contexts == null))
-                return true;
-
-            string assemblyName = __0.Module.Assembly.Name;
-            if (string.IsNullOrEmpty(assemblyName)) 
-                return false;
-
-            AssemblyRewriteContext rewriteContext = null;
-            if (contexts.TryGetValue(assemblyName, out rewriteContext))
-            {
-                //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
-                __result = rewriteContext.TryGetContextForOriginalType(__0);
-                return false;
-            }
+            var klassNamespaceStr = Marshal.PtrToStringAnsi(klassNamespace);
+            if (!string.IsNullOrEmpty(klassNamespaceStr))
+                fullTypeName = $"{klassNamespaceStr}.{klassNameStr}";
+        }
 
-            if (assemblyName.StartsWith("Il2Cpp"))
-                assemblyName = assemblyName.Remove(0, 6);
-            else
-                assemblyName = $"Il2Cpp{assemblyName}";
-            if (contexts.TryGetValue(assemblyName, out rewriteContext))
-            {
-                //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}");
-                __result = rewriteContext.TryGetContextForOriginalType(__0);
-                return false;
-            }
+        var fileName = IL2CPP.il2cpp_image_get_filename(image);
+        if (fileName == IntPtr.Zero)
+            return true;
 
+        var fileNameStr = Marshal.PtrToStringAnsi(fileName);
+        if (string.IsNullOrEmpty(fileNameStr))
             return true;
-        }
 
-        private static bool SystemTypeFromIl2CppType_Prefix(Il2CppTypeStruct* __0, ref Type __result)
+        var il2cppAssemblyPath = Path.Combine(MelonEnvironment.Il2CppAssembliesDirectory, fileNameStr);
+        if (!File.Exists(il2cppAssemblyPath))
+            il2cppAssemblyPath = Path.Combine(MelonEnvironment.Il2CppAssembliesDirectory, $"Il2Cpp{fileNameStr}");
+        if (File.Exists(il2cppAssemblyPath))
         {
-            if ((IntPtr)__0 == IntPtr.Zero)
-                return false;
-			
-            INativeTypeStruct wrappedType = UnityVersionHandler.Wrap(__0);
-            if ((IntPtr)wrappedType.TypePointer == IntPtr.Zero)
-                return false;
-			
-            if (_typeLookup.TryGetValue((IntPtr)wrappedType.TypePointer, out Type type))
+            var asm = Assembly.LoadFrom(il2cppAssemblyPath);
+            if (asm != null)
             {
-                __result = (Type)_rewriteType.Invoke(null, [type]);
-                return false;
+                __result = asm.GetType($"Il2Cpp.{fullTypeName}");
+                if (__result == null)
+                    __result = asm.GetType($"Il2Cpp{fullTypeName}");
+                if (__result == null)
+                    __result = asm.GetType(fullTypeName);
+                if (__result != null)
+                    return false;
             }
+        }
 
-            IntPtr klass = IL2CPP.il2cpp_class_from_type((IntPtr)wrappedType.TypePointer);
-            if (klass == IntPtr.Zero)
-                return true;
-
-            IntPtr image = IL2CPP.il2cpp_class_get_image(klass);
-            if (image == IntPtr.Zero)
-                return true;
+        return true;
+    }
 
-            IntPtr klassNamespace = IL2CPP.il2cpp_class_get_namespace(klass);
-            if (klassNamespace == IntPtr.Zero)
-                return true;
+    private static IEnumerable SystemTypeFromIl2CppType_Transpiler(IEnumerable instructions)
+    {
+        var found = false;
+        foreach (var instruction in instructions)
+        {
+            if (!found
+                && instruction.Calls(_getType))
+            {
+                found = true;
+                instruction.opcode = OpCodes.Call;
+                instruction.operand = _fixedFindType;
 
-            IntPtr klassName = IL2CPP.il2cpp_class_get_name(klass);
-            if (klassName == IntPtr.Zero)
-                return true;
+                LogDebugMsg("Patched Il2CppInterop ClassInjector.SystemTypeFromIl2CppType -> Type.GetType");
+            }
 
-            string klassNameStr = Marshal.PtrToStringAnsi(klassName);
-            if (string.IsNullOrEmpty(klassNameStr))
-                return true;
+            yield return instruction;
+        }
+    }
 
-            string fullTypeName = klassNameStr;
-            if (klassNamespace != IntPtr.Zero)
+    private static IEnumerable RegisterTypeInIl2Cpp_Transpiler(IEnumerable instructions)
+    {
+        var found = false;
+        var found2 = false;
+        foreach (var instruction in instructions)
+        {
+            if (!found
+                && instruction.Calls(_injectorHelpers_AddTypeToLookup))
             {
-                string klassNamespaceStr = Marshal.PtrToStringAnsi(klassNamespace);
-                if (!string.IsNullOrEmpty(klassNamespaceStr))
-                    fullTypeName = $"{klassNamespaceStr}.{klassNameStr}";
+                found = true;
+                instruction.opcode = OpCodes.Call;
+                instruction.operand = _fixedAddTypeToLookup;
+                LogDebugMsg("Patched Il2CppInterop ClassInjector.RegisterTypeInIl2Cpp -> InjectorHelpers.AddTypeToLookup");
             }
 
-            IntPtr fileName = IL2CPP.il2cpp_image_get_filename(image);
-            if (fileName == IntPtr.Zero)
-                return true;
-
-            string fileNameStr = Marshal.PtrToStringAnsi(fileName);
-            if (string.IsNullOrEmpty(fileNameStr))
-                return true;
-
-            string il2cppAssemblyPath = Path.Combine(MelonEnvironment.Il2CppAssembliesDirectory, fileNameStr);
-            if (!File.Exists(il2cppAssemblyPath))
-                il2cppAssemblyPath = Path.Combine(MelonEnvironment.Il2CppAssembliesDirectory, $"Il2Cpp{fileNameStr}");
-            if (File.Exists(il2cppAssemblyPath))
+            if (!found2
+                && instruction.ToString()
+                .Contains("FindAbstractMethods"))
             {
-                Assembly asm = Assembly.LoadFrom(il2cppAssemblyPath);
-                if (asm != null)
-                {
-                    __result = asm.GetType($"Il2Cpp.{fullTypeName}");
-                    if (__result == null)
-                        __result = asm.GetType($"Il2Cpp{fullTypeName}");
-                    if (__result == null)
-                        __result = asm.GetType(fullTypeName);
-                    if (__result != null)
-                        return false;
-                }
+                found2 = true;
+                instruction.opcode = OpCodes.Call;
+                instruction.operand = _fixedFindAbstractMethods;
+                LogDebugMsg("Patched Il2CppInterop ClassInjector.RegisterTypeInIl2Cpp -> FindAbstractMethods");
             }
 
-            return true;
+            yield return instruction;
         }
+    }
 
-        private static IEnumerable SystemTypeFromIl2CppType_Transpiler(IEnumerable instructions)
+    private static IEnumerable ConvertMethodInfo_Transpiler(IEnumerable instructions)
+    {
+        var found = false;
+        foreach (var instruction in instructions)
         {
-            bool found = false;
-            foreach (CodeInstruction instruction in instructions)
+            if (!found
+                && instruction.Calls(_get_IsByRef))
             {
-                if (!found 
-                    && instruction.Calls(_getType))
-                {
-                    found = true;
-                    instruction.opcode = OpCodes.Call;
-                    instruction.operand = _fixedFindType;
-
-                    LogDebugMsg("Patched Il2CppInterop ClassInjector.SystemTypeFromIl2CppType -> Type.GetType");
-                }
-
-                yield return instruction;
+                found = true;
+                instruction.opcode = OpCodes.Call;
+                instruction.operand = _fixedIsByRef;
+                LogDebugMsg("Patched Il2CppInterop ClassInjector.ConvertMethodInfo -> Type.IsByRef");
             }
+
+            yield return instruction;
         }
+    }
 
-        private static IEnumerable RegisterTypeInIl2Cpp_Transpiler(IEnumerable instructions)
+    private static IEnumerable IsTypeSupported_Transpiler(IEnumerable instructions)
+    {
+        var found = false;
+        foreach (var instruction in instructions)
         {
-            bool found = false;
-            bool found2 = false;
-            foreach (CodeInstruction instruction in instructions)
+            if (!found
+                && instruction.Calls(_get_IsByRef))
             {
-                if (!found 
-                    && instruction.Calls(_injectorHelpers_AddTypeToLookup))
-                {
-                    found = true;
-                    instruction.opcode = OpCodes.Call;
-                    instruction.operand = _fixedAddTypeToLookup;
-                    LogDebugMsg("Patched Il2CppInterop ClassInjector.RegisterTypeInIl2Cpp -> InjectorHelpers.AddTypeToLookup");
-                }
-
-                if (!found2
-                    && instruction.ToString()
-                    .Contains("FindAbstractMethods"))
-                {
-                    found2 = true;
-                    instruction.opcode = OpCodes.Call;
-                    instruction.operand = _fixedFindAbstractMethods;
-                    LogDebugMsg("Patched Il2CppInterop ClassInjector.RegisterTypeInIl2Cpp -> FindAbstractMethods");
-                }
-
-                yield return instruction;
+                found = true;
+                instruction.opcode = OpCodes.Call;
+                instruction.operand = _fixedIsByRef;
+                LogDebugMsg("Patched Il2CppInterop ClassInjector.IsTypeSupported -> Type.IsByRef");
             }
+
+            yield return instruction;
         }
+    }
 
+    private static void FixedFindAbstractMethods(List list, INativeClassStruct klass)
+    {
+        if (klass.Parent != default)
+            FixedFindAbstractMethods(list, UnityVersionHandler.Wrap(klass.Parent));
 
-        private static IEnumerable ConvertMethodInfo_Transpiler(IEnumerable instructions)
+        for (var i = 0; i < klass.MethodCount; i++)
         {
-            bool found = false;
-            foreach (CodeInstruction instruction in instructions)
+            var baseMethod = UnityVersionHandler.Wrap(klass.Methods[i]);
+            var name = Marshal.PtrToStringAnsi(baseMethod.Name)!;
+
+            if (baseMethod.Flags.HasFlag(Il2CppMethodFlags.METHOD_ATTRIBUTE_ABSTRACT))
+                list.Add(baseMethod);
+            else
             {
-                if (!found
-                    && instruction.Calls(_get_IsByRef))
+                var existing = list.SingleOrDefault(m =>
                 {
-                    found = true;
-                    instruction.opcode = OpCodes.Call;
-                    instruction.operand = _fixedIsByRef;
-                    LogDebugMsg("Patched Il2CppInterop ClassInjector.ConvertMethodInfo -> Type.IsByRef");
-                }
+                    if (Marshal.PtrToStringAnsi(m.Name) != name)
+                        return false;
+                    if (m.ParametersCount != baseMethod.ParametersCount)
+                        return false;
 
-                yield return instruction;
-            }
-        }
+                    for (var i = 0; i < m.ParametersCount; i++)
+                    {
+                        var parameterName = IL2CPP.il2cpp_method_get_param_name(baseMethod.Pointer, (uint)i);
+                        var otherParameterName = IL2CPP.il2cpp_method_get_param_name(m.Pointer, (uint)i);
 
-        private static IEnumerable IsTypeSupported_Transpiler(IEnumerable instructions)
-        {
-            bool found = false;
-            foreach (CodeInstruction instruction in instructions)
-            {
-                if (!found
-                    && instruction.Calls(_get_IsByRef))
-                {
-                    found = true;
-                    instruction.opcode = OpCodes.Call;
-                    instruction.operand = _fixedIsByRef;
-                    LogDebugMsg("Patched Il2CppInterop ClassInjector.IsTypeSupported -> Type.IsByRef");
-                }
+                        var parameterInfo = UnityVersionHandler.Wrap(baseMethod.Parameters, i);
+                        var otherParameterInfo = UnityVersionHandler.Wrap(m.Parameters, i);
 
-                yield return instruction;
-            }
-        }
+                        if (Marshal.PtrToStringAnsi(parameterName) != Marshal.PtrToStringAnsi(otherParameterName))
+                            return false;
 
-        private static void FixedFindAbstractMethods(List list, INativeClassStruct klass)
-        {
-            if (klass.Parent != default) FixedFindAbstractMethods(list, UnityVersionHandler.Wrap(klass.Parent));
+                        var parameterTypeName = (string)_getIl2CppTypeFullName.Invoke(null, [(IntPtr)parameterInfo.ParameterType]);
+                        var otherParameterTypeName = (string)_getIl2CppTypeFullName.Invoke(null, [(IntPtr)otherParameterInfo.ParameterType]);
 
-            for (var i = 0; i < klass.MethodCount; i++)
-            {
-                var baseMethod = UnityVersionHandler.Wrap(klass.Methods[i]);
-                var name = Marshal.PtrToStringAnsi(baseMethod.Name)!;
+                        if ((parameterTypeName != $"Il2Cpp.{otherParameterTypeName}")
+                            && (parameterTypeName != $"Il2Cpp{otherParameterTypeName}")
+                            && ($"Il2Cpp.{parameterTypeName}" != otherParameterTypeName)
+                            && ($"Il2Cpp{parameterTypeName}" != otherParameterTypeName)
+                            && ($"Il2Cpp.{parameterTypeName}" != $"Il2Cpp.{otherParameterTypeName}")
+                            && ($"Il2Cpp{parameterTypeName}" != $"Il2Cpp{otherParameterTypeName}")
+                            && (parameterTypeName != otherParameterTypeName))
+                            return false;
+                    }
 
-                if (baseMethod.Flags.HasFlag(Il2CppMethodFlags.METHOD_ATTRIBUTE_ABSTRACT))
-                    list.Add(baseMethod);
-                else
-                {
-                    var existing = list.SingleOrDefault(m =>
-                    {
-                        if (Marshal.PtrToStringAnsi(m.Name) != name) return false;
-                        if (m.ParametersCount != baseMethod.ParametersCount) return false;
-
-                        for (var i = 0; i < m.ParametersCount; i++)
-                        {
-                            var parameterName = IL2CPP.il2cpp_method_get_param_name(baseMethod.Pointer, (uint)i);
-                            var otherParameterName = IL2CPP.il2cpp_method_get_param_name(m.Pointer, (uint)i);
-
-                            var parameterInfo = UnityVersionHandler.Wrap(baseMethod.Parameters, i);
-                            var otherParameterInfo = UnityVersionHandler.Wrap(m.Parameters, i);
-
-                            if (Marshal.PtrToStringAnsi(parameterName) != Marshal.PtrToStringAnsi(otherParameterName))
-                                return false;
-
-                            string parameterTypeName = (string)_getIl2CppTypeFullName.Invoke(null, [(IntPtr)parameterInfo.ParameterType]);
-                            string otherParameterTypeName = (string)_getIl2CppTypeFullName.Invoke(null, [(IntPtr)otherParameterInfo.ParameterType]);
-
-                            if ((parameterTypeName != $"Il2Cpp.{otherParameterTypeName}")
-                                && (parameterTypeName != $"Il2Cpp{otherParameterTypeName}")
-                                && ($"Il2Cpp.{parameterTypeName}" != otherParameterTypeName)
-                                && ($"Il2Cpp{parameterTypeName}" != otherParameterTypeName)
-                                && ($"Il2Cpp.{parameterTypeName}" != $"Il2Cpp.{otherParameterTypeName}")
-                                && ($"Il2Cpp{parameterTypeName}" != $"Il2Cpp{otherParameterTypeName}")
-                                && (parameterTypeName != otherParameterTypeName))
-                                return false;
-                        }
-
-                        return true;
-                    });
-
-                    if (existing != null)
-                        list.Remove(existing);
-                }
+                    return true;
+                });
+
+                if (existing != null)
+                    list.Remove(existing);
             }
         }
     }
diff --git a/MelonLoader/Fixes/ProcessFix.cs b/MelonLoader/Fixes/ProcessFix.cs
index e338f8cfe..f213cc024 100644
--- a/MelonLoader/Fixes/ProcessFix.cs
+++ b/MelonLoader/Fixes/ProcessFix.cs
@@ -48,6 +48,7 @@ private static bool get_MainWindowTitle(Process __instance, ref string __result)
             GetWindowText(new HandleRef(__instance, intPtr), stringBuilder, stringBuilder.Capacity);
             __result = stringBuilder.ToString();
         }
+
         return false;
     }
 
diff --git a/MelonLoader/Fixes/ServerCertificateValidation.cs b/MelonLoader/Fixes/ServerCertificateValidation.cs
index 392408564..6f6df7ab1 100644
--- a/MelonLoader/Fixes/ServerCertificateValidation.cs
+++ b/MelonLoader/Fixes/ServerCertificateValidation.cs
@@ -54,6 +54,7 @@ private static bool CertificateValidation(object sender, X509Certificate certifi
             if (!chainIsValid)
                 return false;
         }
+
         return true;
     }
 #else
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
index 653d92e03..1aa15ebf8 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
@@ -24,7 +24,7 @@ public class BZip2InputStream : Stream
     private const int NO_RAND_PART_C_STATE = 7;
 
 #if VECTORIZE_MEMORY_MOVE
-		private static readonly int VectorSize = System.Numerics.Vector.Count;
+    private static readonly int VectorSize = System.Numerics.Vector.Count;
 #endif // VECTORIZE_MEMORY_MOVE
 
     #endregion Constants
@@ -268,8 +268,10 @@ public override int Read(byte[] buffer, int offset, int count)
             {
                 return i;
             }
+
             buffer[offset + i] = (byte)rb;
         }
+
         return count;
     }
 
@@ -319,6 +321,7 @@ public override int ReadByte()
             case RAND_PART_A_STATE:
                 break;
         }
+
         return retChar;
     }
 
@@ -515,6 +518,7 @@ private void RecvDecodingTables()
             {
                 j++;
             }
+
             selectorMtf[i] = (byte)j;
         }
 
@@ -534,6 +538,7 @@ private void RecvDecodingTables()
                 pos[v] = pos[v - 1];
                 v--;
             }
+
             pos[0] = tmp;
             selector[i] = tmp;
         }
@@ -555,6 +560,7 @@ private void RecvDecodingTables()
                         curr--;
                     }
                 }
+
                 len[t][i] = (char)curr;
             }
         }
@@ -569,6 +575,7 @@ private void RecvDecodingTables()
                 maxLen = Math.Max(maxLen, len[t][i]);
                 minLen = Math.Min(minLen, len[t][i]);
             }
+
             HbCreateDecodeTables(limit[t], baseArray[t], perm[t], len[t], minLen, maxLen, alphaSize);
             minLens[t] = minLen;
         }
@@ -623,19 +630,23 @@ cache misses.
             { // the longest code
                 throw new BZip2Exception("Bzip data error");
             }
+
             zn++;
             while (bsLive < 1)
             {
                 FillBuffer();
             }
+
             zj = (bsBuff >> (bsLive - 1)) & 1;
             bsLive--;
             zvec = (zvec << 1) | zj;
         }
+
         if (zvec - baseArray[zt][zn] is < 0 or >= BZip2Constants.MaximumAlphaSize)
         {
             throw new BZip2Exception("Bzip data error");
         }
+
         nextSym = perm[zt][zvec - baseArray[zt][zn]];
 
         while (true)
@@ -681,10 +692,12 @@ cache misses.
                         {
                             FillBuffer();
                         }
+
                         zj = (bsBuff >> (bsLive - 1)) & 1;
                         bsLive--;
                         zvec = (zvec << 1) | zj;
                     }
+
                     nextSym = perm[zt][zvec - baseArray[zt][zn]];
                 } while (nextSym is BZip2Constants.RunA or BZip2Constants.RunB);
 
@@ -703,6 +716,7 @@ cache misses.
                 {
                     BlockOverrun();
                 }
+
                 continue;
             }
             else
@@ -720,17 +734,17 @@ cache misses.
                 var j = nextSym - 1;
 
 #if VECTORIZE_MEMORY_MOVE
-					// This is vectorized memory move. Going from the back, we're taking chunks of array
-					// and write them at the new location shifted by one. Since chunks are VectorSize long,
-					// at the end we have to move "tail" (or head actually) of the array using a plain loop.
-					// If System.Numerics.Vector API is not available, the plain loop is used to do the whole copying.
-
-					while(j >= VectorSize)
-					{
-						var arrayPart = new System.Numerics.Vector(yy, j - VectorSize);
-						arrayPart.CopyTo(yy, j - VectorSize + 1);
-						j -= VectorSize;
-					}
+                // This is vectorized memory move. Going from the back, we're taking chunks of array
+                // and write them at the new location shifted by one. Since chunks are VectorSize long,
+                // at the end we have to move "tail" (or head actually) of the array using a plain loop.
+                // If System.Numerics.Vector API is not available, the plain loop is used to do the whole copying.
+
+                while (j >= VectorSize)
+                {
+                    var arrayPart = new System.Numerics.Vector(yy, j - VectorSize);
+                    arrayPart.CopyTo(yy, j - VectorSize + 1);
+                    j -= VectorSize;
+                }
 #endif // VECTORIZE_MEMORY_MOVE
 
                 while (j > 0)
@@ -757,10 +771,12 @@ cache misses.
                     {
                         FillBuffer();
                     }
+
                     zj = (bsBuff >> (bsLive - 1)) & 1;
                     bsLive--;
                     zvec = (zvec << 1) | zj;
                 }
+
                 nextSym = perm[zt][zvec - baseArray[zt][zn]];
                 continue;
             }
@@ -820,6 +836,7 @@ private void SetupRandPartA()
                     rTPos = 0;
                 }
             }
+
             rNToGo--;
             ch2 ^= (rNToGo == 1) ? 1 : 0;
             i2++;
@@ -881,6 +898,7 @@ private void SetupRandPartB()
                         rTPos = 0;
                     }
                 }
+
                 rNToGo--;
                 z ^= (byte)((rNToGo == 1) ? 1 : 0);
                 j2 = 0;
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
index 4f5abfd8c..5aba054e1 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
@@ -144,6 +144,7 @@ public BZip2OutputStream(Stream stream, int blockSize)
         {
             blockSize = 1;
         }
+
         blockSize100k = blockSize;
         AllocateCompressStructures();
         Initialize();
@@ -598,10 +599,12 @@ private void BsW(int n, int v)
             {
                 baseStream.WriteByte((byte)ch);
             } // write 8-bit
+
             bsBuff <<= 8;
             bsLive -= 8;
             ++bytesOut;
         }
+
         bsBuff |= v << (32 - bsLive - n);
         bsLive += n;
     }
@@ -651,18 +654,7 @@ private void SendMTFValues()
             Panic();
         }
 
-        if (nMTF < 200)
-        {
-            nGroups = 2;
-        }
-        else if (nMTF < 600)
-        {
-            nGroups = 3;
-        }
-        else
-        {
-            nGroups = nMTF < 1200 ? 4 : nMTF < 2400 ? 5 : 6;
-        }
+        nGroups = nMTF < 200 ? 2 : nMTF < 600 ? 3 : nMTF < 1200 ? 4 : nMTF < 2400 ? 5 : 6;
 
         /*--- Generate an initial set of coding tables ---*/
         var nPart = nGroups;
@@ -731,6 +723,7 @@ Iterate up to N_ITERS times to improve the tables.
                 {
                     break;
                 }
+
                 ge = gs + BZip2Constants.GroupSize - 1;
                 if (ge >= nMTF)
                 {
@@ -760,6 +753,7 @@ by each of the coding tables.
                         cost4 += (short)len[4][icv];
                         cost5 += (short)len[5][icv];
                     }
+
                     cost[0] = cost0;
                     cost[1] = cost1;
                     cost[2] = cost2;
@@ -793,6 +787,7 @@ and record its identity in the selector table.
                         bt = t;
                     }
                 }
+
                 totc += bc;
                 fave[bt]++;
                 selector[nSelectors] = (char)bt;
@@ -849,6 +844,7 @@ Recompute the tables based on the accumulated frequencies.
                 tmp = pos[j];
                 pos[j] = tmp2;
             }
+
             pos[0] = tmp;
             selectorMtf[i] = (char)j;
         }
@@ -871,19 +867,23 @@ Recompute the tables based on the accumulated frequencies.
                 {
                     maxLen = len[t][i];
                 }
+
                 if (len[t][i] < minLen)
                 {
                     minLen = len[t][i];
                 }
             }
+
             if (maxLen > 20)
             {
                 Panic();
             }
+
             if (minLen < 1)
             {
                 Panic();
             }
+
             HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize);
         }
 
@@ -940,6 +940,7 @@ Recompute the tables based on the accumulated frequencies.
             {
                 BsW(1, 1);
             }
+
             BsW(1, 0);
         }
 
@@ -955,11 +956,13 @@ Recompute the tables based on the accumulated frequencies.
                     BsW(2, 2);
                     curr++; /* 10 */
                 }
+
                 while (curr > len[t][i])
                 {
                     BsW(2, 3);
                     curr--; /* 11 */
                 }
+
                 BsW(1, 0);
             }
         }
@@ -973,6 +976,7 @@ Recompute the tables based on the accumulated frequencies.
             {
                 break;
             }
+
             ge = gs + BZip2Constants.GroupSize - 1;
             if (ge >= nMTF)
             {
@@ -987,6 +991,7 @@ Recompute the tables based on the accumulated frequencies.
             gs = ge + 1;
             ++selCtr;
         }
+
         if (!(selCtr == nSelectors))
         {
             Panic();
@@ -1016,6 +1021,7 @@ private void SimpleSort(int lo, int hi, int d)
         {
             hp++;
         }
+
         hp--;
 
         for (; hp >= 0; hp--)
@@ -1037,6 +1043,7 @@ private void SimpleSort(int lo, int hi, int d)
                     if (j <= (lo + h - 1))
                         break;
                 }
+
                 zptr[j] = v;
                 i++;
 
@@ -1045,6 +1052,7 @@ private void SimpleSort(int lo, int hi, int d)
                 {
                     break;
                 }
+
                 v = zptr[i];
                 j = i;
                 while (FullGtU(zptr[j - h] + d, v + d))
@@ -1056,6 +1064,7 @@ private void SimpleSort(int lo, int hi, int d)
                         break;
                     }
                 }
+
                 zptr[j] = v;
                 i++;
 
@@ -1064,6 +1073,7 @@ private void SimpleSort(int lo, int hi, int d)
                 {
                     break;
                 }
+
                 v = zptr[i];
                 j = i;
                 while (FullGtU(zptr[j - h] + d, v + d))
@@ -1075,6 +1085,7 @@ private void SimpleSort(int lo, int hi, int d)
                         break;
                     }
                 }
+
                 zptr[j] = v;
                 i++;
 
@@ -1132,6 +1143,7 @@ private void QSort3(int loSt, int hiSt, int dSt)
                 {
                     return;
                 }
+
                 continue;
             }
 
@@ -1150,6 +1162,7 @@ private void QSort3(int loSt, int hiSt, int dSt)
                     {
                         break;
                     }
+
                     n = block[zptr[unLo] + d + 1] - med;
                     if (n == 0)
                     {
@@ -1160,10 +1173,12 @@ private void QSort3(int loSt, int hiSt, int dSt)
                         unLo++;
                         continue;
                     }
+
                     if (n > 0)
                     {
                         break;
                     }
+
                     unLo++;
                 }
 
@@ -1173,6 +1188,7 @@ private void QSort3(int loSt, int hiSt, int dSt)
                     {
                         break;
                     }
+
                     n = block[zptr[unHi] + d + 1] - med;
                     if (n == 0)
                     {
@@ -1183,10 +1199,12 @@ private void QSort3(int loSt, int hiSt, int dSt)
                         unHi--;
                         continue;
                     }
+
                     if (n < 0)
                     {
                         break;
                     }
+
                     unHi--;
                 }
 
@@ -1258,6 +1276,7 @@ private void MainSort()
         {
             block[last + i + 2] = block[(i % (last + 1)) + 1];
         }
+
         for (i = 0; i <= last + BZip2Constants.OvershootBytes; i++)
         {
             quadrant[i] = 0;
@@ -1275,6 +1294,7 @@ has quite a large constant overhead.
             {
                 zptr[i] = i;
             }
+
             firstAttempt = false;
             workDone = workLimit = 0;
             SimpleSort(0, last, 0);
@@ -1286,6 +1306,7 @@ has quite a large constant overhead.
             {
                 bigDone[i] = false;
             }
+
             for (i = 0; i <= 65536; i++)
             {
                 ftab[i] = 0;
@@ -1351,6 +1372,7 @@ big bucket.
                             break;
                         }
                     }
+
                     runningOrder[j] = vv;
                 }
             } while (h != 1);
@@ -1388,6 +1410,7 @@ we don't have to sort them at all.
                                 return;
                             }
                         }
+
                         ftab[sb] |= SETMASK;
                     }
                 }
@@ -1478,6 +1501,7 @@ private void RandomiseBlock()
                     rTPos = 0;
                 }
             }
+
             rNToGo--;
             block[i + 1] ^= (byte)((rNToGo == 1) ? 1 : 0);
             // handle 16 bit signed numbers
@@ -1533,6 +1557,7 @@ private bool FullGtU(int i1, int i2)
         {
             return c1 > c2;
         }
+
         i1++;
         i2++;
 
@@ -1542,6 +1567,7 @@ private bool FullGtU(int i1, int i2)
         {
             return c1 > c2;
         }
+
         i1++;
         i2++;
 
@@ -1551,6 +1577,7 @@ private bool FullGtU(int i1, int i2)
         {
             return c1 > c2;
         }
+
         i1++;
         i2++;
 
@@ -1560,6 +1587,7 @@ private bool FullGtU(int i1, int i2)
         {
             return c1 > c2;
         }
+
         i1++;
         i2++;
 
@@ -1569,6 +1597,7 @@ private bool FullGtU(int i1, int i2)
         {
             return c1 > c2;
         }
+
         i1++;
         i2++;
 
@@ -1578,6 +1607,7 @@ private bool FullGtU(int i1, int i2)
         {
             return c1 > c2;
         }
+
         i1++;
         i2++;
 
@@ -1591,12 +1621,14 @@ private bool FullGtU(int i1, int i2)
             {
                 return c1 > c2;
             }
+
             s1 = quadrant[i1];
             s2 = quadrant[i2];
             if (s1 != s2)
             {
                 return s1 > s2;
             }
+
             i1++;
             i2++;
 
@@ -1606,12 +1638,14 @@ private bool FullGtU(int i1, int i2)
             {
                 return c1 > c2;
             }
+
             s1 = quadrant[i1];
             s2 = quadrant[i2];
             if (s1 != s2)
             {
                 return s1 > s2;
             }
+
             i1++;
             i2++;
 
@@ -1621,12 +1655,14 @@ private bool FullGtU(int i1, int i2)
             {
                 return c1 > c2;
             }
+
             s1 = quadrant[i1];
             s2 = quadrant[i2];
             if (s1 != s2)
             {
                 return s1 > s2;
             }
+
             i1++;
             i2++;
 
@@ -1636,12 +1672,14 @@ private bool FullGtU(int i1, int i2)
             {
                 return c1 > c2;
             }
+
             s1 = quadrant[i1];
             s2 = quadrant[i2];
             if (s1 != s2)
             {
                 return s1 > s2;
             }
+
             i1++;
             i2++;
 
@@ -1650,6 +1688,7 @@ private bool FullGtU(int i1, int i2)
                 i1 -= last;
                 i1--;
             }
+
             if (i2 > last)
             {
                 i2 -= last;
@@ -1732,6 +1771,7 @@ private void GenerateMTFValues()
                 tmp = yy[j];
                 yy[j] = tmp2;
             }
+
             yy[0] = tmp;
 
             if (j == 0)
@@ -1759,14 +1799,18 @@ private void GenerateMTFValues()
                                 mtfFreq[BZip2Constants.RunB]++;
                                 break;
                         }
+
                         if (zPend < 2)
                         {
                             break;
                         }
+
                         zPend = (zPend - 2) / 2;
                     }
+
                     zPend = 0;
                 }
+
                 szptr[wr] = (short)(j + 1);
                 wr++;
                 mtfFreq[j + 1]++;
@@ -1792,10 +1836,12 @@ private void GenerateMTFValues()
                         mtfFreq[BZip2Constants.RunB]++;
                         break;
                 }
+
                 if (zPend < 2)
                 {
                     break;
                 }
+
                 zPend = (zPend - 2) / 2;
             }
         }
@@ -1851,8 +1897,10 @@ Nodes and heap entries run from 1.  Entry 0
                     heap[zz] = heap[zz >> 1];
                     zz >>= 1;
                 }
+
                 heap[zz] = tmp;
             }
+
             if (!(nHeap < (BZip2Constants.MaximumAlphaSize + 2)))
             {
                 Panic();
@@ -1873,10 +1921,12 @@ Nodes and heap entries run from 1.  Entry 0
                     {
                         break;
                     }
+
                     if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]])
                     {
                         yy++;
                     }
+
                     if (weight[tmp] < weight[heap[yy]])
                     {
                         break;
@@ -1885,6 +1935,7 @@ Nodes and heap entries run from 1.  Entry 0
                     heap[zz] = heap[yy];
                     zz = yy;
                 }
+
                 heap[zz] = tmp;
                 n2 = heap[1];
                 heap[1] = heap[nHeap];
@@ -1899,17 +1950,21 @@ Nodes and heap entries run from 1.  Entry 0
                     {
                         break;
                     }
+
                     if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]])
                     {
                         yy++;
                     }
+
                     if (weight[tmp] < weight[heap[yy]])
                     {
                         break;
                     }
+
                     heap[zz] = heap[yy];
                     zz = yy;
                 }
+
                 heap[zz] = tmp;
                 nNodes++;
                 parent[n1] = parent[n2] = nNodes;
@@ -1928,8 +1983,10 @@ Nodes and heap entries run from 1.  Entry 0
                     heap[zz] = heap[zz >> 1];
                     zz >>= 1;
                 }
+
                 heap[zz] = tmp;
             }
+
             if (!(nNodes < (BZip2Constants.MaximumAlphaSize * 2)))
             {
                 Panic();
@@ -1945,6 +2002,7 @@ Nodes and heap entries run from 1.  Entry 0
                     k = parent[k];
                     j++;
                 }
+
                 len[i - 1] = (char)j;
                 tooLong |= j > maxLen;
             }
@@ -1976,6 +2034,7 @@ private static void HbAssignCodes(int[] code, char[] length, int minLen, int max
                     ++vec;
                 }
             }
+
             vec <<= 1;
         }
     }
@@ -1989,14 +2048,17 @@ private static byte Med3(byte a, byte b, byte c)
             a = b;
             b = t;
         }
+
         if (b > c)
         {
             b = c;
         }
+
         if (a > b)
         {
             b = a;
         }
+
         return b;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
index a39586100..b01eaae25 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
@@ -148,15 +148,18 @@ public void Update(ArraySegment segment)
             {
                 n = count;
             }
+
             count -= n;
             while (--n >= 0)
             {
                 s1 += (uint)(segment.Array[offset++] & 0xff);
                 s2 += s1;
             }
+
             s1 %= BASE;
             s2 %= BASE;
         }
+
         checkValue = (s2 << 16) | s1;
     }
 }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs
index 3f2c05275..85b263d6e 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib;
+namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
 
 /// 
 /// SharpZipBaseException is the base exception class for SharpZipLib.
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs
index 60cb3ede7..85f79a3bf 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib;
+namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
 
 /// 
 /// Indicates that an error occurred during decoding of a input stream due to corrupt
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs
index 3c1214345..a74a77a7f 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib;
+namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
 
 /// 
 /// Indicates that the input stream could not decoded due to known library incompability or missing features
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs
index fe7cd0d4b..063a41541 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib;
+namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
 
 /// 
 /// Indicates that the input stream could not decoded due to the stream ending before enough data had been provided
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
index fcd7c9acd..c056119fc 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib;
+namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
 
 /// 
 /// Indicates that a value was outside of the expected range when decoding an input stream
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
index 017505970..90a01be3c 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
@@ -352,6 +352,7 @@ private bool OnDirectoryFailure(string directory, Exception e)
             handler(this, args);
             alive_ = args.ContinueRunning;
         }
+
         return result;
     }
 
@@ -372,6 +373,7 @@ private bool OnFileFailure(string file, Exception e)
             FileFailure(this, args);
             alive_ = args.ContinueRunning;
         }
+
         return result;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
index e37caefa7..81392aa46 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
@@ -53,6 +53,7 @@ public static bool IsValidExpression(string expression)
         {
             result = false;
         }
+
         return result;
     }
 
@@ -176,6 +177,7 @@ public bool IsIncluded(string name)
                 }
             }
         }
+
         return result;
     }
 
@@ -195,6 +197,7 @@ public bool IsExcluded(string name)
                 break;
             }
         }
+
         return result;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs
index 98d68c461..fae685367 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs
@@ -40,6 +40,7 @@ public virtual bool IsMatch(string name)
             var cooked = (name.Length > 0) ? Path.GetFullPath(name) : "";
             result = nameFilter_.IsMatch(cooked);
         }
+
         return result;
     }
 
@@ -133,6 +134,7 @@ public override bool IsMatch(string name)
                 (MaxDate >= fileInfo.LastWriteTime)
                 ;
         }
+
         return result;
     }
 
@@ -271,6 +273,7 @@ public override bool IsMatch(string name)
                 (MinSize <= length) &&
                 (MaxSize >= length);
         }
+
         return result;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs b/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
index 70737b4c5..9d1a8c6c1 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
@@ -59,6 +59,7 @@ public static void ReadFully(Stream stream, byte[] buffer, int offset, int count
             {
                 throw new EndOfStreamException();
             }
+
             offset += readCount;
             count -= readCount;
         }
@@ -104,6 +105,7 @@ public static int ReadRequestedBytes(Stream stream, byte[] buffer, int offset, i
             {
                 break;
             }
+
             offset += readCount;
             count -= readCount;
             totalReadCount += readCount;
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
index f4b0582c5..01911d956 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
@@ -175,6 +175,7 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
             outputBuffer[outputOffset++] = (byte)(inputBuffer[i] ^ TransformByte());
             UpdateKeys(oldbyte);
         }
+
         return inputCount;
     }
 
@@ -285,6 +286,7 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
             outputBuffer[outputOffset++] = newByte;
             UpdateKeys(newByte);
         }
+
         return inputCount;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
index ea7531755..417d1ba8b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
@@ -125,9 +125,11 @@ private int ReadAndTransform(byte[] buffer, int offset, int count)
                 {
                     _slideBuffer[iTo] = _slideBuffer[iFrom];
                 }
+
                 _slideBufFreePos -= _slideBufStartPos;      // Note the -=
                 _slideBufStartPos = 0;
             }
+
             var obtained = StreamUtils.ReadRequestedBytes(_stream, _slideBuffer, _slideBufFreePos, lengthToRead);
             _slideBufFreePos += obtained;
 
@@ -167,6 +169,7 @@ private int ReadAndTransform(byte[] buffer, int offset, int count)
                 break;  // Reached the auth code
             }
         }
+
         return nBytes;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
index e5694ccab..0f3f34bef 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
@@ -23,6 +23,7 @@ public byte[] GetHashAndReset()
                 TransformFinalBlock(dummy, 0, 0);
                 _finalised = true;
             }
+
             return Hash;
         }
     }
@@ -116,15 +117,18 @@ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, b
                 _encryptor.TransformBlock(_counterNonce, 0, _blockSize, _encryptBuffer, 0);
                 _encrPos = 0;
             }
+
             outputBuffer[ix + outputOffset] = (byte)(inputBuffer[ix + inputOffset] ^ _encryptBuffer[_encrPos++]);
             //
             ix++;
         }
+
         if (_writeMode)
         {
             // This does not change the buffer.
             _hmacsha1.AppendData(outputBuffer, outputOffset, inputCount);
         }
+
         return inputCount;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
index 32938ef74..471a84c4b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
@@ -197,6 +197,7 @@ private bool ReadHeader()
         {
             throw new GZipException("Error GZIP header,  second magic byte doesn't match");
         }
+
         headCRC.Update(magic);
 
         // 2. Check the compression type (must be 8)
@@ -206,6 +207,7 @@ private bool ReadHeader()
         {
             throw new GZipException("Error GZIP header, data not in deflate format");
         }
+
         headCRC.Update(compressionType);
 
         // 3. Check the flags
@@ -257,6 +259,7 @@ private bool ReadHeader()
                 {
                     fname[fnamePos++] = (byte)readByte;
                 }
+
                 headCRC.Update(readByte);
             }
 
@@ -326,6 +329,7 @@ private void ReadFooter()
             {
                 throw new EndOfStreamException("EOS reading GZIP footer");
             }
+
             needed -= count; // Jewel Jan 16
         }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
index 9e2f5a1cc..400055d31 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
@@ -452,6 +452,7 @@ public int RecordSize
             {
                 return tarOut.RecordSize;
             }
+
             return TarBuffer.DefaultRecordSize;
         }
     }
@@ -505,6 +506,7 @@ public void ListContents()
             {
                 break;
             }
+
             OnProgressMessageEvent(entry, null);
         }
     }
@@ -612,7 +614,7 @@ private void ExtractEntry(string destDir, TarEntry entry, bool allowParentTraver
             if (process)
             {
                 using var outputStream = File.Create(destFile);
-                if (this.asciiTranslate)
+                if (asciiTranslate)
                 {
                     // May need to translate the file.
                     ExtractAndTranslateEntry(destFile, outputStream);
@@ -695,6 +697,7 @@ public void WriteEntry(TarEntry sourceEntry, bool recurse)
                 TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName,
                                            sourceEntry.GroupId, sourceEntry.GroupName);
             }
+
             WriteEntryCore(sourceEntry, recurse);
         }
         finally
@@ -752,6 +755,7 @@ private void WriteEntryCore(TarEntry sourceEntry, bool recurse)
                         {
                             break;
                         }
+
                         var data = Encoding.ASCII.GetBytes(line);
                         outStream.Write(data, 0, data.Length);
                         outStream.WriteByte((byte)'\n');
@@ -909,6 +913,7 @@ private static bool IsBinary(string filename)
                 return true;
             }
         }
+
         return false;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
index 3073d9408..da2bfc8f5 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
@@ -568,6 +568,7 @@ public void Close()
             {
                 outputStream.Dispose();
             }
+
             outputStream = null;
         }
         else if (inputStream != null)
@@ -576,6 +577,7 @@ public void Close()
             {
                 inputStream.Dispose();
             }
+
             inputStream = null;
         }
     }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs
index 2ca88311f..48872cd3b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs
@@ -356,6 +356,7 @@ public bool IsDirectory
                     return true;
                 }
             }
+
             return false;
         }
     }
@@ -382,7 +383,7 @@ public void GetFileTarHeader(TarHeader header, string file)
         var name = file;
 
         // 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory
-        if (name.IndexOf(Directory.GetCurrentDirectory(), StringComparison.Ordinal) == 0)
+        if (name.StartsWith(Directory.GetCurrentDirectory(), StringComparison.Ordinal))
         {
             name = name[Directory.GetCurrentDirectory().Length..];
         }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs
index fc8e480f0..aba44ff6a 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs
@@ -335,6 +335,7 @@ public long Size
             {
                 throw new ArgumentOutOfRangeException(nameof(value), "Cannot be less than zero");
             }
+
             size = value;
         }
     }
@@ -355,6 +356,7 @@ public DateTime ModTime
             {
                 throw new ArgumentOutOfRangeException(nameof(value), "ModTime cannot be before Jan 1st 1970");
             }
+
             modTime = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second);
         }
     }
@@ -446,6 +448,7 @@ public string UserName
                 {
                     currentUser = currentUser[..UNAMELEN];
                 }
+
                 userName = currentUser;
             }
         }
@@ -494,7 +497,7 @@ public int DevMinor
     /// A new  that is a copy of the current instance.
     public object Clone()
     {
-        return this.MemberwiseClone();
+        return MemberwiseClone();
     }
 
     #endregion ICloneable Members
@@ -716,8 +719,10 @@ private static long ParseBinaryOrOctal(byte[] header, int offset, int length)
             {
                 result = (result << 8) | header[offset + pos];
             }
+
             return result;
         }
+
         return ParseOctal(header, offset, length);
     }
 
@@ -839,6 +844,7 @@ public static StringBuilder ParseName(byte[] header, int offset, int length, Enc
                 {
                     break;
                 }
+
                 result.Append((char)header[i]);
             }
         }
@@ -851,6 +857,7 @@ public static StringBuilder ParseName(byte[] header, int offset, int length, Enc
                     break;
                 }
             }
+
             result.Append(encoding.GetString(header, offset, count));
         }
 
@@ -929,6 +936,7 @@ public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int b
         {
             buffer[bufferOffset + i] = 0;
         }
+
         return bufferOffset + length;
     }
     /// 
@@ -1141,9 +1149,11 @@ private static int GetBinaryOrOctalBytes(long value, byte[] buffer, int offset,
                 buffer[offset + pos] = (byte)value;
                 value >>= 8;
             }
+
             buffer[offset] = 0x80;
             return offset + length;
         }
+
         return GetOctalBytes(value, buffer, offset, length);
     }
 
@@ -1177,6 +1187,7 @@ private static int ComputeCheckSum(byte[] buffer)
         {
             sum += buffer[i];
         }
+
         return sum;
     }
 
@@ -1202,6 +1213,7 @@ private static int MakeCheckSum(byte[] buffer)
         {
             sum += buffer[i];
         }
+
         return sum;
     }
 
@@ -1222,6 +1234,7 @@ private static DateTime GetDateTimeFromCTime(long ticks)
         {
             result = dateTime1970;
         }
+
         return result;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs
index 34cc55532..3ac41aff5 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs
@@ -205,6 +205,7 @@ public override int ReadByte()
             // return -1 to indicate that no byte was read.
             return -1;
         }
+
         return oneByteBuffer[0];
     }
 
@@ -482,21 +483,22 @@ public TarEntry GetNextEntry()
                 {
                     throw new TarException("Header checksum is invalid");
                 }
-                this.entryOffset = 0;
-                this.entrySize = header.Size;
+
+                entryOffset = 0;
+                entrySize = header.Size;
 
                 StringBuilder longName = null;
 
                 if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME)
                 {
                     var nameBuffer = new byte[TarBuffer.BlockSize];
-                    var numToRead = this.entrySize;
+                    var numToRead = entrySize;
 
                     longName = new StringBuilder();
 
                     while (numToRead > 0)
                     {
-                        var numRead = this.Read(nameBuffer, 0, numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead);
+                        var numRead = Read(nameBuffer, 0, numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead);
 
                         if (numRead == -1)
                         {
@@ -508,24 +510,24 @@ public TarEntry GetNextEntry()
                     }
 
                     SkipToNextEntry();
-                    headerBuf = this.tarBuffer.ReadBlock();
+                    headerBuf = tarBuffer.ReadBlock();
                 }
                 else if (header.TypeFlag == TarHeader.LF_GHDR)
                 {  // POSIX global extended header
                    // Ignore things we dont understand completely for now
                     SkipToNextEntry();
-                    headerBuf = this.tarBuffer.ReadBlock();
+                    headerBuf = tarBuffer.ReadBlock();
                 }
                 else if (header.TypeFlag == TarHeader.LF_XHDR)
                 {  // POSIX extended header
                     var nameBuffer = new byte[TarBuffer.BlockSize];
-                    var numToRead = this.entrySize;
+                    var numToRead = entrySize;
 
                     var xhr = new TarExtendedHeaderReader();
 
                     while (numToRead > 0)
                     {
-                        var numRead = this.Read(nameBuffer, 0, numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead);
+                        var numRead = Read(nameBuffer, 0, numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead);
 
                         if (numRead == -1)
                         {
@@ -542,13 +544,13 @@ public TarEntry GetNextEntry()
                     }
 
                     SkipToNextEntry();
-                    headerBuf = this.tarBuffer.ReadBlock();
+                    headerBuf = tarBuffer.ReadBlock();
                 }
                 else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR)
                 {
                     // TODO: could show volume name when verbose
                     SkipToNextEntry();
-                    headerBuf = this.tarBuffer.ReadBlock();
+                    headerBuf = tarBuffer.ReadBlock();
                 }
                 else if (header.TypeFlag is not TarHeader.LF_NORMAL and
                      not TarHeader.LF_OLDNORM and
@@ -580,7 +582,7 @@ not TarHeader.LF_SYMLINK and
                 entryOffset = 0;
 
                 // TODO: Review How do we resolve this discrepancy?!
-                entrySize = this.currentEntry.Size;
+                entrySize = currentEntry.Size;
             }
             catch (InvalidHeaderException ex)
             {
@@ -592,6 +594,7 @@ not TarHeader.LF_SYMLINK and
                 throw new InvalidHeaderException(errorText);
             }
         }
+
         return currentEntry;
     }
 
@@ -613,6 +616,7 @@ public void CopyEntryContents(Stream outputStream)
             {
                 break;
             }
+
             outputStream.Write(tempBuffer, 0, numRead);
         }
     }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs
index 0e874706a..6b2b277ea 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs
@@ -201,6 +201,7 @@ public void Finish()
         {
             CloseEntry();
         }
+
         WriteEofBlock();
     }
 
@@ -291,7 +292,7 @@ public void PutNextEntry(TarEntry entry)
             while (nameCharIndex < namelen + 1 /* we've allocated one for the null char, now we must make sure it gets written out */)
             {
                 Array.Clear(blockBuffer, 0, blockBuffer.Length);
-                TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize, nameEncoding); // This func handles OK the extra char out of string length
+                TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, blockBuffer, 0, TarBuffer.BlockSize, nameEncoding); // This func handles OK the extra char out of string length
                 nameCharIndex += TarBuffer.BlockSize;
                 buffer.WriteBlock(blockBuffer);
             }
@@ -390,7 +391,7 @@ public override void Write(byte[] buffer, int offset, int count)
         if ((currBytes + count) > currSize)
         {
             var errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes",
-                count, this.currSize);
+                count, currSize);
             throw new ArgumentOutOfRangeException(nameof(count), errorText);
         }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs
index 3d19b89da..d139efb74 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs
@@ -334,6 +334,7 @@ public void SetInput(byte[] input, int offset, int count)
         {
             throw new InvalidOperationException("Finish() already called");
         }
+
         engine.SetInput(input, offset, count);
     }
 
@@ -443,12 +444,14 @@ public int Deflate(byte[] output, int offset, int length)
             {
                 level_flags = 3;
             }
+
             header |= level_flags << 6;
             if ((state & IS_SETDICT) != 0)
             {
                 // Dictionary was set
                 header |= DeflaterConstants.PRESET_DICT;
             }
+
             header += 31 - (header % 31);
 
             pending.WriteShortMSB(header);
@@ -500,6 +503,7 @@ public int Deflate(byte[] output, int offset, int length)
                                 neededbits -= 10;
                             }
                         }
+
                         state = BUSY_STATE;
                         break;
 
@@ -513,11 +517,13 @@ public int Deflate(byte[] output, int offset, int length)
                             pending.WriteShortMSB(adler >> 16);
                             pending.WriteShortMSB(adler & 0xffff);
                         }
+
                         state = FINISHED_STATE;
                         break;
                 }
             }
         }
+
         return origLength - length;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs
index 4dad1bf7d..2d9e44ddc 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs
@@ -222,6 +222,7 @@ public void SetDictionary(byte[] buffer, int offset, int length)
             InsertString();
             strstart++;
         }
+
         strstart += 2;
         blockStart = strstart;
     }
@@ -328,6 +329,7 @@ public void SetLevel(int level)
                             strstart - blockStart, false);
                         blockStart = strstart;
                     }
+
                     UpdateHash();
                     break;
 
@@ -338,6 +340,7 @@ public void SetLevel(int level)
                             false);
                         blockStart = strstart;
                     }
+
                     break;
 
                 case DeflaterConstants.DEFLATE_SLOW:
@@ -345,15 +348,18 @@ public void SetLevel(int level)
                     {
                         huffman.TallyLit(window[strstart - 1] & 0xff);
                     }
+
                     if (strstart > blockStart)
                     {
                         huffman.FlushBlock(window, blockStart, strstart - blockStart, false);
                         blockStart = strstart;
                     }
+
                     prevAvailable = false;
                     matchLen = DeflaterConstants.MIN_MATCH - 1;
                     break;
             }
+
             compressionFunction = DeflaterConstants.COMPR_FUNC[level];
         }
     }
@@ -479,7 +485,7 @@ private bool FindLongestMatch(int curMatch)
 
         var window = this.window;
         var prev = this.prev;
-        var chainLength = this.max_chain;
+        var chainLength = max_chain;
         var niceLength = Math.Min(this.niceLength, lookahead);
 
         matchLen = Math.Max(matchLen, DeflaterConstants.MIN_MATCH - 1);
@@ -491,7 +497,7 @@ private bool FindLongestMatch(int curMatch)
         var scan_end = window[scan + matchLen];
 
         // Do not waste too much time if we already have a good match:
-        if (matchLen >= this.goodLength)
+        if (matchLen >= goodLength)
             chainLength >>= 2;
 
         do
@@ -652,6 +658,7 @@ private bool DeflateStored(bool flush, bool finish)
             blockStart += storedLength;
             return !(lastBlock || storedLength == 0);
         }
+
         return true;
     }
 
@@ -710,6 +717,7 @@ private bool DeflateFast(bool flush, bool finish)
                         ++strstart;
                         InsertString();
                     }
+
                     ++strstart;
                 }
                 else
@@ -720,6 +728,7 @@ private bool DeflateFast(bool flush, bool finish)
                         UpdateHash();
                     }
                 }
+
                 matchLen = DeflaterConstants.MIN_MATCH - 1;
                 if (!full)
                 {
@@ -742,6 +751,7 @@ private bool DeflateFast(bool flush, bool finish)
                 return !lastBlock;
             }
         }
+
         return true;
     }
 
@@ -760,6 +770,7 @@ private bool DeflateSlow(bool flush, bool finish)
                 {
                     huffman.TallyLit(window[strstart - 1] & 0xff);
                 }
+
                 prevAvailable = false;
 
                 // We are flushing everything
@@ -840,6 +851,7 @@ private bool DeflateSlow(bool flush, bool finish)
                 {
                     huffman.TallyLit(window[strstart - 1] & 0xff);
                 }
+
                 prevAvailable = true;
                 strstart++;
                 lookahead--;
@@ -852,12 +864,14 @@ private bool DeflateSlow(bool flush, bool finish)
                 {
                     len--;
                 }
+
                 var lastBlock = finish && (lookahead == 0) && !prevAvailable;
                 huffman.FlushBlock(window, blockStart, len, lastBlock);
                 blockStart += len;
                 return !lastBlock;
             }
         }
+
         return true;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs
index 4dc7210e8..6aad21927 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs
@@ -84,7 +84,7 @@ private class Tree
         public Tree(DeflaterHuffman dh, int elems, int minCodes, int maxLength)
         {
             this.dh = dh;
-            this.minNumCodes = minCodes;
+            minNumCodes = minCodes;
             this.maxLength = maxLength;
             freqs = new short[elems];
             bl_counts = new int[maxLength];
@@ -101,6 +101,7 @@ public void Reset()
             {
                 freqs[i] = 0;
             }
+
             codes = null;
             length = null;
         }
@@ -221,6 +222,7 @@ public void BuildTree()
                         heap[pos] = heap[ppos];
                         pos = ppos;
                     }
+
                     heap[pos] = n;
 
                     maxCode = n;
@@ -285,6 +287,7 @@ public void BuildTree()
                 {
                     heap[path] = heap[ppos];
                 }
+
                 heap[path] = last;
 
                 var second = heap[0];
@@ -317,6 +320,7 @@ public void BuildTree()
                 {
                     heap[path] = heap[ppos];
                 }
+
                 heap[path] = last;
             } while (heapLen > 1);
 
@@ -339,6 +343,7 @@ public int GetEncodedLength()
             {
                 len += freqs[i] * length[i];
             }
+
             return len;
         }
 
@@ -373,6 +378,7 @@ public void CalcBLFreq(Tree blTree)
                         count = 0;
                     }
                 }
+
                 curlen = nextlen;
                 i++;
 
@@ -435,6 +441,7 @@ public void WriteTree(Tree blTree)
                         count = 0;
                     }
                 }
+
                 curlen = nextlen;
                 i++;
 
@@ -474,7 +481,7 @@ public void WriteTree(Tree blTree)
 
         private void BuildLength(int[] childs)
         {
-            this.length = new byte[freqs.Length];
+            length = new byte[freqs.Length];
             var numNodes = childs.Length / 2;
             var numLeafs = (numNodes + 1) / 2;
             var overflow = 0;
@@ -498,6 +505,7 @@ private void BuildLength(int[] childs)
                         bitLength = maxLength;
                         overflow++;
                     }
+
                     lengths[childs[2 * i]] = lengths[childs[(2 * i) + 1]] = bitLength;
                 }
                 else
@@ -505,7 +513,7 @@ private void BuildLength(int[] childs)
                     // A leaf node
                     var bitLength = lengths[i];
                     bl_counts[bitLength - 1]++;
-                    this.length[childs[2 * i]] = (byte)lengths[i];
+                    length[childs[2 * i]] = (byte)lengths[i];
                 }
             }
 
@@ -685,6 +693,7 @@ public void SendAllTrees(int blTreeCodes)
         {
             pending.WriteBits(blTree.length[BL_ORDER[rank]], 3);
         }
+
         literalTree.WriteTree(blTree);
         distTree.WriteTree(blTree);
 
@@ -808,6 +817,7 @@ public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool l
                 blTreeCodes = i + 1;
             }
         }
+
         var opt_len = 14 + (blTreeCodes * 3) + blTree.GetEncodedLength() +
             literalTree.GetEncodedLength() + distTree.GetEncodedLength() +
             extra_bits;
@@ -817,10 +827,12 @@ public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool l
         {
             static_len += literalTree.freqs[i] * staticLLength[i];
         }
+
         for (var i = 0; i < DIST_NUM; i++)
         {
             static_len += distTree.freqs[i] * staticDLength[i];
         }
+
         if (opt_len >= static_len)
         {
             // Force static trees
@@ -913,6 +925,7 @@ public bool TallyDist(int distance, int length)
         {
             extra_bits += (dc / 2) - 1;
         }
+
         return IsFull();
     }
 
@@ -942,6 +955,7 @@ private static int Lcode(int length)
             code += 4;
             length >>= 1;
         }
+
         return code + length;
     }
 
@@ -953,6 +967,7 @@ private static int Dcode(int distance)
             code += 2;
             distance >>= 1;
         }
+
         return code + distance;
     }
 }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs
index 69585d777..404a2e7ea 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs
@@ -173,7 +173,7 @@ public Inflater(bool noHeader)
     {
         this.noHeader = noHeader;
         if (!noHeader)
-            this.adler = new Adler32();
+            adler = new Adler32();
         input = new StreamManipulator();
         outputWindow = new OutputWindow();
         mode = noHeader ? DECODE_BLOCKS : DECODE_HEADER;
@@ -215,6 +215,7 @@ private bool DecodeHeader()
         {
             return false;
         }
+
         input.DropBits(16);
 
         // The header is written in "wrong" byte order
@@ -245,6 +246,7 @@ private bool DecodeHeader()
             mode = DECODE_DICT;
             neededBits = 32;
         }
+
         return true;
     }
 
@@ -263,10 +265,12 @@ private bool DecodeDict()
             {
                 return false;
             }
+
             input.DropBits(8);
             readAdler = (readAdler << 8) | dictByte;
             neededBits -= 8;
         }
+
         return false;
     }
 
@@ -324,6 +328,7 @@ private bool DecodeHuffman()
                     {
                         throw new SharpZipBaseException("Illegal rep length code");
                     }
+
                     goto case DECODE_HUFFMAN_LENBITS; // fall through
 
                 case DECODE_HUFFMAN_LENBITS:
@@ -335,9 +340,11 @@ private bool DecodeHuffman()
                         {
                             return false;
                         }
+
                         input.DropBits(neededBits);
                         repLength += i;
                     }
+
                     mode = DECODE_HUFFMAN_DIST;
                     goto case DECODE_HUFFMAN_DIST; // fall through
 
@@ -369,6 +376,7 @@ private bool DecodeHuffman()
                         {
                             return false;
                         }
+
                         input.DropBits(neededBits);
                         repDist += i;
                     }
@@ -382,6 +390,7 @@ private bool DecodeHuffman()
                     throw new SharpZipBaseException("Inflater unknown mode");
             }
         }
+
         return true;
     }
 
@@ -403,6 +412,7 @@ private bool DecodeChksum()
             {
                 return false;
             }
+
             input.DropBits(8);
             readAdler = (readAdler << 8) | chkByte;
             neededBits -= 8;
@@ -461,6 +471,7 @@ private bool Decode()
                 {
                     return false;
                 }
+
                 input.DropBits(3);
 
                 isLastBlock |= (type & 1) != 0;
@@ -485,6 +496,7 @@ private bool Decode()
                     default:
                         throw new SharpZipBaseException("Unknown block type " + type);
                 }
+
                 return true;
 
             case DECODE_STORED_LEN1:
@@ -493,9 +505,11 @@ private bool Decode()
                     {
                         return false;
                     }
+
                     input.DropBits(16);
                     mode = DECODE_STORED_LEN2;
                 }
+
                 goto case DECODE_STORED_LEN2; // fall through
 
             case DECODE_STORED_LEN2:
@@ -505,13 +519,16 @@ private bool Decode()
                     {
                         return false;
                     }
+
                     input.DropBits(16);
                     if (nlen != (uncomprLen ^ 0xffff))
                     {
                         throw new SharpZipBaseException("broken uncompressed block");
                     }
+
                     mode = DECODE_STORED;
                 }
+
                 goto case DECODE_STORED; // fall through
 
             case DECODE_STORED:
@@ -523,6 +540,7 @@ private bool Decode()
                         mode = DECODE_BLOCKS;
                         return true;
                     }
+
                     return !input.IsNeedingInput;
                 }
 
@@ -614,6 +632,7 @@ public void SetDictionary(byte[] buffer, int index, int count)
         {
             throw new SharpZipBaseException("Wrong adler checksum");
         }
+
         adler?.Reset();
         outputWindow.CopyDict(buffer, index, count);
         mode = DECODE_BLOCKS;
@@ -736,6 +755,7 @@ public int Inflate(byte[] buffer, int offset, int count)
             { // -jr- 08-Nov-2003 INFLATE_BUG fix..
                 Decode();
             }
+
             return 0;
         }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs
index e277b9d29..8ab423698 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs
@@ -41,18 +41,22 @@ static InflaterHuffmanTree()
             {
                 codeLengths[i++] = 8;
             }
+
             while (i < 256)
             {
                 codeLengths[i++] = 9;
             }
+
             while (i < 280)
             {
                 codeLengths[i++] = 7;
             }
+
             while (i < 288)
             {
                 codeLengths[i++] = 8;
             }
+
             defLitLenTree = new InflaterHuffmanTree(codeLengths);
 
             codeLengths = new byte[32];
@@ -61,6 +65,7 @@ static InflaterHuffmanTree()
             {
                 codeLengths[i++] = 5;
             }
+
             defDistTree = new InflaterHuffmanTree(codeLengths);
         }
         catch (Exception)
@@ -143,6 +148,7 @@ private void BuildTree(IList codeLengths)
             {
                 continue;
             }
+
             code = nextCode[bits];
             int revcode = DeflaterHuffman.BitReverse(code);
             if (bits <= 9)
@@ -164,6 +170,7 @@ private void BuildTree(IList codeLengths)
                     revcode += 1 << bits;
                 } while (revcode < treeLen);
             }
+
             nextCode[bits] = code + (1 << (16 - bits));
         }
     }
@@ -192,9 +199,11 @@ public int GetSymbol(StreamManipulator input)
                 {
                     throw new SharpZipBaseException("Encountered invalid codelength 0");
                 }
+
                 input.DropBits(bitlen);
                 return symbol >> 4;
             }
+
             var subtree = -(symbol >> 4);
             if ((lookahead = input.PeekBits(bitlen)) >= 0)
             {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs
index 5264e5a22..0a765ce9c 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs
@@ -156,6 +156,7 @@ public void AlignToByte()
                 buffer[end++] = unchecked((byte)(bits >> 8));
             }
         }
+
         bits = 0;
         bitCount = 0;
     }
@@ -244,6 +245,7 @@ public int Flush(byte[] output, int offset, int length)
             System.Array.Copy(buffer, start, output, offset, length);
             start += length;
         }
+
         return length;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
index 7e1f9c1ec..0327d3b13 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs
@@ -126,6 +126,7 @@ public virtual void Finish()
             {
                 AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode();
             }
+
             cryptoTransform_.Dispose();
             cryptoTransform_ = null;
         }
@@ -204,6 +205,7 @@ private void Deflate(bool flushing)
             {
                 break;
             }
+
             if (cryptoTransform_ != null)
             {
                 EncryptBlock(buffer_, 0, deflateCount);
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
index 0f1f85484..211dd9573 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
@@ -35,6 +35,7 @@ public InflaterInputBuffer(Stream stream, int bufferSize)
         {
             bufferSize = 1024;
         }
+
         rawData = new byte[bufferSize];
         clearText = rawData;
     }
@@ -123,6 +124,7 @@ public void Fill()
             {
                 break;
             }
+
             rawLength += count;
             toRead -= count;
         }
@@ -169,12 +171,14 @@ public int ReadRawBuffer(byte[] outBuffer, int offset, int length)
                     return 0;
                 }
             }
+
             var toCopy = Math.Min(currentLength, available);
             System.Array.Copy(rawData, rawLength - available, outBuffer, currentOffset, toCopy);
             currentOffset += toCopy;
             currentLength -= toCopy;
             available -= toCopy;
         }
+
         return length;
     }
 
@@ -212,6 +216,7 @@ public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length)
             currentLength -= toCopy;
             available -= toCopy;
         }
+
         return length;
     }
 
@@ -229,6 +234,7 @@ public byte ReadLeByte()
                 throw new ZipException("EOF in header");
             }
         }
+
         var result = rawData[rawLength - available];
         available -= 1;
         return result;
@@ -277,6 +283,7 @@ public ICryptoTransform CryptoTransform
                     internalClearText ??= new byte[rawData.Length];
                     clearText = internalClearText;
                 }
+
                 clearTextLength = rawLength;
                 if (available > 0)
                 {
@@ -369,7 +376,7 @@ public InflaterInputStream(Stream baseInputStream, Inflater inflater, int buffer
         }
 
         this.baseInputStream = baseInputStream ?? throw new ArgumentNullException(nameof(baseInputStream));
-        this.inf = inflater ?? throw new ArgumentNullException(nameof(inflater));
+        inf = inflater ?? throw new ArgumentNullException(nameof(inflater));
 
         inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize);
     }
@@ -473,6 +480,7 @@ protected void Fill()
                 throw new SharpZipBaseException("Unexpected EOF");
             }
         }
+
         inputBuffer.SetInflaterInput(inf);
     }
 
@@ -656,6 +664,7 @@ public override int Read(byte[] buffer, int offset, int count)
                 throw new ZipException("Invalid input data");
             }
         }
+
         return count - remainingBytes;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs
index 892abf4e4..25b0f6801 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs
@@ -38,6 +38,7 @@ public void Write(int value)
         {
             throw new InvalidOperationException("Window full");
         }
+
         window[windowEnd++] = (byte)value;
         windowEnd &= WindowMask;
     }
@@ -147,6 +148,7 @@ public void CopyDict(byte[] dictionary, int offset, int length)
             offset += length - WindowSize;
             length = WindowSize;
         }
+
         System.Array.Copy(dictionary, offset, window, 0, length);
         windowEnd = length & WindowMask;
     }
@@ -200,6 +202,7 @@ public int CopyOutput(byte[] output, int offset, int len)
             offset += tailLen;
             len = copyEnd;
         }
+
         System.Array.Copy(window, copyEnd - len, output, offset, len);
         windowFilled -= copied;
         return windowFilled < 0 ? throw new InvalidOperationException() : copied;
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs
index 58f276b97..1d484aad7 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs
@@ -36,10 +36,12 @@ public int PeekBits(int bitCount)
             {
                 return -1; // ok
             }
+
             buffer_ |= (uint)(((window_[windowStart_++] & 0xff) |
                              ((window_[windowStart_++] & 0xff) << 8)) << bitsInBuffer_);
             bitsInBuffer_ += 16;
         }
+
         return (int)(buffer_ & ((1 << bitCount) - 1));
     }
 
@@ -55,6 +57,7 @@ public bool TryGetBits(int bitCount, ref int output, int outputOffset = 0)
         {
             return false;
         }
+
         output = bits + outputOffset;
         DropBits(bitCount);
         return true;
@@ -72,6 +75,7 @@ public bool TryGetBits(int bitCount, ref byte[] array, int index)
         {
             return false;
         }
+
         array[index] = (byte)bits;
         DropBits(bitCount);
         return true;
@@ -104,6 +108,7 @@ public int GetBits(int bitCount)
         {
             DropBits(bitCount);
         }
+
         return bits;
     }
 
@@ -213,6 +218,7 @@ public int CopyBytes(byte[] output, int offset, int length)
         {
             length = avail;
         }
+
         System.Array.Copy(window_, windowStart_, output, offset, length);
         windowStart_ += length;
 
@@ -222,6 +228,7 @@ public int CopyBytes(byte[] output, int offset, int length)
             buffer_ = (uint)(window_[windowStart_++] & 0xff);
             bitsInBuffer_ = 8;
         }
+
         return count + length;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs
index 89c8d0196..da3a8f307 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs
@@ -59,6 +59,7 @@ public bool OnDirectoryFailure(string directory, Exception e)
             handler(this, args);
             result = args.ContinueRunning;
         }
+
         return result;
     }
 
@@ -79,6 +80,7 @@ public bool OnFileFailure(string file, Exception e)
             handler(this, args);
             result = args.ContinueRunning;
         }
+
         return result;
     }
 
@@ -98,6 +100,7 @@ public bool OnProcessFile(string file)
             handler(this, args);
             result = args.ContinueRunning;
         }
+
         return result;
     }
 
@@ -116,6 +119,7 @@ public bool OnCompletedFile(string file)
             handler(this, args);
             result = args.ContinueRunning;
         }
+
         return result;
     }
 
@@ -135,6 +139,7 @@ public bool OnProcessDirectory(string directory, bool hasMatchingFiles)
             handler(this, args);
             result = args.ContinueRunning;
         }
+
         return result;
     }
 
@@ -462,7 +467,7 @@ private void CreateZip(Stream outputStream, string sourceDirectory, bool recurse
 
             outputStream_.UseZip64 = UseZip64;
             scanner.ProcessFile += ProcessFile;
-            if (this.CreateEmptyDirectories)
+            if (CreateEmptyDirectories)
             {
                 scanner.ProcessDirectory += ProcessDirectory;
             }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs
index 0d4aa6330..d4dced728 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs
@@ -108,6 +108,7 @@ public string TransformDirectory(string name)
         {
             throw new InvalidNameException("Cannot have an empty directory name");
         }
+
         return name;
     }
 
@@ -150,6 +151,7 @@ public string TransformFile(string name)
         {
             name = string.Empty;
         }
+
         return name;
     }
 
@@ -217,6 +219,7 @@ public static string MakeValidName(string name, char replacement)
 
                 index = index >= name.Length ? -1 : name.IndexOfAny(InvalidEntryChars, index + 1);
             }
+
             name = builder.ToString();
         }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
index bd375ae32..68b7b7d40 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
@@ -210,10 +210,10 @@ internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo,
             throw new ArgumentOutOfRangeException(nameof(versionRequiredToExtract));
         }
 
-        this.DateTime = DateTime.Now;
+        DateTime = DateTime.Now;
         this.name = name;
-        this.versionMadeBy = (ushort)madeByInfo;
-        this.versionToExtract = (ushort)versionRequiredToExtract;
+        versionMadeBy = (ushort)madeByInfo;
+        versionToExtract = (ushort)versionRequiredToExtract;
         this.method = method;
 
         IsUnicodeText = ZipStrings.UseUnicode;
@@ -398,6 +398,7 @@ private bool HasDosAttributes(int attributes)
                 (HostSystem == (int)HostSystemID.WindowsNT)) &&
                 (ExternalFileAttributes & attributes) == attributes;
         }
+
         return result;
     }
 
@@ -492,13 +493,11 @@ public int Version
                 // Ver 5.1 = AES
                 return ZipConstants.VERSION_AES;
 
-            if (CompressionMethod.BZip2 == method)
-                return ZipConstants.VersionBZip2;
-
-            if (CentralHeaderRequiresZip64)
-                return ZipConstants.VersionZip64;
-
-            return CompressionMethod.Deflated == method || IsDirectory || IsCrypted ? 20 : HasDosAttributes(0x08) ? 11 : 10;
+            return CompressionMethod.BZip2 == method
+                ? ZipConstants.VersionBZip2
+                : CentralHeaderRequiresZip64
+                ? ZipConstants.VersionZip64
+                : CompressionMethod.Deflated == method || IsDirectory || IsCrypted ? 20 : HasDosAttributes(0x08) ? 11 : 10;
         }
     }
 
@@ -540,13 +539,13 @@ public bool LocalHeaderRequiresZip64
 
                 if ((versionToExtract == 0) && IsCrypted)
                 {
-                    trueCompressedSize += (ulong)this.EncryptionOverheadSize;
+                    trueCompressedSize += (ulong)EncryptionOverheadSize;
                 }
 
                 // TODO: A better estimation of the true limit based on compression overhead should be used
                 // to determine when an entry should use Zip64.
                 result =
-                    ((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) &&
+                    ((size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) &&
                     ((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64));
             }
 
@@ -709,8 +708,9 @@ public long Crc
             {
                 throw new ArgumentOutOfRangeException(nameof(value));
             }
-            this.crc = (uint)value;
-            this.known |= Known.Crc;
+
+            crc = (uint)value;
+            known |= Known.Crc;
         }
     }
 
@@ -843,7 +843,7 @@ public int AESKeySize
     /// 
     internal void ProcessExtraData(bool localHeader)
     {
-        var extraData = new ZipExtraData(this.extra);
+        var extraData = new ZipExtraData(extra);
 
         if (extraData.Find(0x0001))
         {
@@ -1029,7 +1029,7 @@ public bool IsDirectory
     /// An  that is a copy of the current instance.
     public object Clone()
     {
-        var result = (ZipEntry)this.MemberwiseClone();
+        var result = (ZipEntry)MemberwiseClone();
 
         // Ensure extra data is unique if it exists.
         if (extra != null)
@@ -1092,6 +1092,7 @@ public static string CleanName(string name)
         {
             name = name.Remove(0, 1);
         }
+
         return name;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs
index 40c03972e..a69b2f22f 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs
@@ -130,6 +130,7 @@ public DateTime FixedDateTime
             {
                 throw new ArgumentException("Value is too old to be valid", nameof(value));
             }
+
             fixedDateTime_ = value;
         }
     }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs
index 9388b03c9..2a42e16e4 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs
@@ -204,18 +204,21 @@ public byte[] GetData()
             var seconds = (int)span.TotalSeconds;
             helperStream.WriteLEInt(seconds);
         }
+
         if ((_flags & Flags.AccessTime) != 0)
         {
             var span = _lastAccessTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
             var seconds = (int)span.TotalSeconds;
             helperStream.WriteLEInt(seconds);
         }
+
         if ((_flags & Flags.CreateTime) != 0)
         {
             var span = _createTime - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
             var seconds = (int)span.TotalSeconds;
             helperStream.WriteLEInt(seconds);
         }
+
         return ms.ToArray();
     }
 
@@ -358,6 +361,7 @@ public void SetData(byte[] data, int index, int count)
                     var createTimeTicks = helperStream.ReadLELong();
                     _createTime = DateTime.FromFileTimeUtc(createTimeTicks);
                 }
+
                 break;
             }
             else
@@ -408,6 +412,7 @@ public static bool IsValidValue(DateTime value)
         {
             result = false;
         }
+
         return result;
     }
 
@@ -423,6 +428,7 @@ public DateTime LastModificationTime
             {
                 throw new ArgumentOutOfRangeException(nameof(value));
             }
+
             _lastModificationTime = value;
         }
     }
@@ -439,6 +445,7 @@ public DateTime CreateTime
             {
                 throw new ArgumentOutOfRangeException(nameof(value));
             }
+
             _createTime = value;
         }
     }
@@ -455,6 +462,7 @@ public DateTime LastAccessTime
             {
                 throw new ArgumentOutOfRangeException(nameof(value));
             }
+
             _lastAccessTime = value;
         }
     }
@@ -558,6 +566,7 @@ public Stream GetStreamForTag(int tag)
         {
             result = new MemoryStream(_data, _index, _readValueLength, false);
         }
+
         return result;
     }
 
@@ -660,6 +669,7 @@ public void AddEntry(ITaggedData taggedData)
         {
             throw new ArgumentNullException(nameof(taggedData));
         }
+
         AddEntry(taggedData.TagID, taggedData.GetData());
     }
 
@@ -817,6 +827,7 @@ public bool Delete(int headerID)
             Array.Copy(_data, trueEnd, newData, trueStart, _data.Length - trueEnd);
             _data = newData;
         }
+
         return result;
     }
 
@@ -870,6 +881,7 @@ public int ReadByte()
             result = _data[_index];
             _index += 1;
         }
+
         return result;
     }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs
index abd30091b..c01e27778 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs
@@ -758,6 +758,7 @@ public int FindEntry(string name, bool ignoreCase)
                 return i;
             }
         }
+
         return -1;
     }
 
@@ -821,6 +822,7 @@ public Stream GetInputStream(ZipEntry entry)
                 throw new ZipException("Entry cannot be found");
             }
         }
+
         return GetInputStream(index);
     }
 
@@ -959,7 +961,7 @@ public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandl
 
                     var crc = new Crc32();
 
-                    using (var entryStream = this.GetInputStream(this[entryIndex]))
+                    using (var entryStream = GetInputStream(this[entryIndex]))
                     {
                         var buffer = new byte[4096];
                         long totalBytes = 0;
@@ -1493,6 +1495,7 @@ public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataS
             update.OffsetBasedSize = updates_[idx + 1].Entry.Offset - update.Entry.Offset;
             idx++;
         }
+
         updateCount_ = updates_.Count;
 
         contentsEdited_ = false;
@@ -1949,6 +1952,7 @@ public bool Delete(string fileName)
         {
             throw new ZipException("Cannot find entry to delete");
         }
+
         return result;
     }
 
@@ -2077,6 +2081,7 @@ private void WriteLocalEntryHeader(ZipUpdate update)
                     {
                         entry.ForceZip64();
                     }
+
                     break;
 
                 case UseZip64.On:
@@ -2437,6 +2442,7 @@ private void CopyBytes(ZipUpdate update, Stream destination, Stream source,
                 {
                     crc.Update(new ArraySegment(buffer, 0, bytesRead));
                 }
+
                 destination.Write(buffer, 0, bytesRead);
                 bytesToCopy -= bytesRead;
                 totalBytesRead += bytesRead;
@@ -2535,6 +2541,7 @@ private void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc
                 {
                     crc.Update(new ArraySegment(buffer, 0, bytesRead));
                 }
+
                 stream.Position = destinationPosition;
                 stream.Write(buffer, 0, bytesRead);
 
@@ -2629,6 +2636,7 @@ private Stream GetOutputStream(ZipEntry entry)
                     // otherwise, wrap the base stream in an UncompressedStream instead of returning it directly
                     result = new UncompressedStream(result);
                 }
+
                 break;
 
             case CompressionMethod.Deflated:
@@ -2654,6 +2662,7 @@ private Stream GetOutputStream(ZipEntry entry)
             default:
                 throw new ZipException("Unknown compression method " + entry.CompressionMethod);
         }
+
         return result;
     }
 
@@ -2723,7 +2732,7 @@ private void ModifyEntry(ZipFile workFile, ZipUpdate update)
         if (update.Entry.IsFile && (update.Filename != null))
         {
             using var output = workFile.GetOutputStream(update.OutEntry);
-            using var source = this.GetInputStream(update.Entry);
+            using var source = GetInputStream(update.Entry);
             CopyBytes(update, output, source, source.Length, true);
         }
 
@@ -2786,6 +2795,7 @@ private void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destin
             {
                 CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition);
             }
+
             CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition);
         }
     }
@@ -2810,6 +2820,7 @@ private void CopyEntry(ZipFile workFile, ZipUpdate update)
 
             CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false);
         }
+
         CopyDescriptorBytes(update, workFile.baseStream_, baseStream_);
     }
 
@@ -2934,6 +2945,7 @@ public int Compare(ZipUpdate x, ZipUpdate y)
                     result = offsetDiff < 0 ? -1 : offsetDiff == 0 ? 0 : 1;
                 }
             }
+
             return result;
         }
     }
@@ -2991,6 +3003,7 @@ private void RunUpdates()
                             {
                                 CopyEntry(workFile, update);
                             }
+
                             break;
 
                         case UpdateCommand.Modify:
@@ -3010,6 +3023,7 @@ private void RunUpdates()
                             {
                                 destinationPosition = workFile.baseStream_.Position;
                             }
+
                             break;
                     }
                 }
@@ -3075,6 +3089,7 @@ private void RunUpdates()
             {
                 File.Delete(workFile.Name);
             }
+
             throw;
         }
 
@@ -3610,6 +3625,7 @@ private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
                 {
                     throw new ZipException("No password available for AES encrypted stream");
                 }
+
                 var saltLen = entry.AESSaltLen;
                 var saltBytes = new byte[saltLen];
                 var saltIn = StreamUtils.ReadRequestedBytes(baseStream, saltBytes, 0, saltLen);
@@ -3685,6 +3701,7 @@ private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
                 WriteEncryptionHeader(result, entry.Crc);
             }
         }
+
         return result;
     }
 
@@ -4140,11 +4157,13 @@ public override int Read(byte[] buffer, int offset, int count)
                 {
                     baseStream_.Seek(readPos_, SeekOrigin.Begin);
                 }
+
                 var readCount = baseStream_.Read(buffer, offset, count);
                 if (readCount > 0)
                 {
                     readPos_ += readCount;
                 }
+
                 return readCount;
             }
         }
@@ -4217,6 +4236,7 @@ public override long Seek(long offset, SeekOrigin origin)
             {
                 throw new IOException("Cannot seek past end");
             }
+
             readPos_ = newPos;
             return readPos_;
         }
@@ -4254,6 +4274,7 @@ public override long Position
                 {
                     throw new InvalidOperationException("Cannot seek past end");
                 }
+
                 readPos_ = newPos;
             }
         }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs
index 1954a634a..2a433405b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs
@@ -216,6 +216,7 @@ private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData)
             {
                 patchData.CrcPatchOffset = stream_.Position;
             }
+
             WriteLEInt(0);  // Crc
 
             if (patchData != null)
@@ -258,6 +259,7 @@ private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData)
                 ed.AddLeLong(-1);
                 ed.AddLeLong(-1);
             }
+
             ed.AddNewEntry(1);
 
             if (!ed.Find(1))
@@ -321,6 +323,7 @@ public long LocateBlockWithSignature(int signature, long endLocation, int minimu
             {
                 return -1;
             }
+
             Seek(pos--, SeekOrigin.Begin);
         } while (ReadLEInt() != signature);
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs
index e99c46e59..633cbd0da 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs
@@ -315,6 +315,7 @@ private void ReadDataDescriptor()
             csize = inputBuffer.ReadLeInt();
             size = inputBuffer.ReadLeInt();
         }
+
         entry.CompressedSize = csize;
         entry.Size = size;
     }
@@ -346,6 +347,7 @@ private void CompleteCloseEntry(bool testCrc)
         {
             inf.Reset();
         }
+
         entry = null;
     }
 
@@ -381,6 +383,7 @@ public void CloseEntry()
                 while (Read(tmp, 0, tmp.Length) > 0)
                 {
                 }
+
                 return;
             }
 
@@ -564,17 +567,11 @@ private int InitialRead(byte[] destination, int offset, int count)
     /// Zero bytes read means end of stream.
     public override int Read(byte[] buffer, int offset, int count)
     {
-        if (buffer == null)
-        {
-            throw new ArgumentNullException(nameof(buffer));
-        }
-
-        if (offset < 0)
-        {
-            throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
-        }
-
-        return count < 0
+        return buffer == null
+            ? throw new ArgumentNullException(nameof(buffer))
+            : offset < 0
+            ? throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative")
+            : count < 0
             ? throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative")
             : (buffer.Length - offset) < count
             ? throw new ArgumentException("Invalid offset/count combination")
@@ -625,6 +622,7 @@ private int BodyRead(byte[] buffer, int offset, int count)
                     {
                         throw new ZipException("Inflater not finished!");
                     }
+
                     inputBuffer.Available = inf.RemainingInput;
 
                     // A csize of -1 is from an unpatched local header
@@ -633,9 +631,11 @@ private int BodyRead(byte[] buffer, int offset, int count)
                     {
                         throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut);
                     }
+
                     inf.Reset();
                     finished = true;
                 }
+
                 break;
 
             case CompressionMethod.Stored:
@@ -665,6 +665,7 @@ private int BodyRead(byte[] buffer, int offset, int count)
                         throw new ZipException("EOF in stored block");
                     }
                 }
+
                 break;
         }
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs
index 7761c783a..190238295 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs
@@ -74,6 +74,7 @@ public string TransformDirectory(string name)
         {
             throw new ZipException("Cannot have an empty directory name");
         }
+
         return name;
     }
 
@@ -87,7 +88,7 @@ public string TransformFile(string name)
         if (name != null)
         {
             var lowerName = name.ToLower();
-            if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0))
+            if ((trimPrefix_ != null) && lowerName.StartsWith(trimPrefix_, StringComparison.Ordinal))
             {
                 name = name[trimPrefix_.Length..];
             }
@@ -112,6 +113,7 @@ public string TransformFile(string name)
         {
             name = string.Empty;
         }
+
         return name;
     }
 
@@ -152,6 +154,7 @@ private static string MakeValidName(string name, char replacement)
 
                 index = index >= name.Length ? -1 : name.IndexOfAny(InvalidEntryChars, index + 1);
             }
+
             name = builder.ToString();
         }
 
@@ -179,7 +182,7 @@ public static bool IsValidName(string name, bool relaxed)
             result = relaxed
             ? name.IndexOfAny(InvalidEntryCharsRelaxed) < 0
             : (name.IndexOfAny(InvalidEntryChars) < 0) &&
-                (name.IndexOf('/') != 0);
+                (!name.StartsWith('/'));
         }
 
         return result;
@@ -202,7 +205,7 @@ public static bool IsValidName(string name)
         var result =
             (name != null) &&
             (name.IndexOfAny(InvalidEntryChars) < 0) &&
-            (name.IndexOf('/') != 0)
+            (!name.StartsWith('/'))
             ;
         return result;
     }
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs
index 7852e502a..523d27c16 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs
@@ -109,6 +109,7 @@ public void SetComment(string comment)
         {
             throw new ArgumentOutOfRangeException(nameof(comment));
         }
+
         zipComment = commentBytes;
     }
 
@@ -210,9 +211,9 @@ private void WriteLeLong(long value)
     // Apply any configured transforms/cleaning to the name of the supplied entry.
     private void TransformEntryName(ZipEntry entry)
     {
-        if (this.NameTransform != null)
+        if (NameTransform != null)
         {
-            entry.Name = entry.IsDirectory ? this.NameTransform.TransformDirectory(entry.Name) : this.NameTransform.TransformFile(entry.Name);
+            entry.Name = entry.IsDirectory ? NameTransform.TransformDirectory(entry.Name) : NameTransform.TransformFile(entry.Name);
         }
     }
 
@@ -274,7 +275,7 @@ public void PutNextEntry(ZipEntry entry)
         }
 
         // A password must have been set in order to add AES encrypted entries
-        if (entry.AESKeySize > 0 && string.IsNullOrEmpty(this.Password))
+        if (entry.AESKeySize > 0 && string.IsNullOrEmpty(Password))
         {
             throw new InvalidOperationException("The Password property must be set before AES encrypted entries can be added");
         }
@@ -387,6 +388,7 @@ public void PutNextEntry(ZipEntry entry)
             {
                 crcPatchPos = baseOutputStream_.Position;
             }
+
             WriteLeInt(0);  // Crc
 
             if (patchEntryHeader)
@@ -431,6 +433,7 @@ public void PutNextEntry(ZipEntry entry)
                 ed.AddLeLong(-1);
                 ed.AddLeLong(-1);
             }
+
             ed.AddNewEntry(1);
 
             if (!ed.Find(1))
@@ -452,6 +455,7 @@ public void PutNextEntry(ZipEntry entry)
         {
             AddExtraDataAES(entry, ed);
         }
+
         var extra = ed.GetEntryData();
 
         WriteLeShort(name.Length);
@@ -485,6 +489,7 @@ public void PutNextEntry(ZipEntry entry)
             deflater_.Reset();
             deflater_.SetLevel(compressionLevel);
         }
+
         size = 0;
 
         if (entry.IsCrypted)
@@ -617,6 +622,7 @@ public void CloseEntry()
                 WriteLeInt((int)curEntry.CompressedSize);
                 WriteLeInt((int)curEntry.Size);
             }
+
             baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
         }
 
@@ -784,6 +790,7 @@ public override void Write(byte[] buffer, int offset, int count)
                 {
                     baseOutputStream_.Write(buffer, offset, count);
                 }
+
                 break;
         }
     }
@@ -903,6 +910,7 @@ public override void Finish()
             {
                 AddExtraDataAES(entry, ed);
             }
+
             var extra = ed.GetEntryData();
 
             var entryComment =
diff --git a/MelonLoader/InternalUtils/DependencyGraph.cs b/MelonLoader/InternalUtils/DependencyGraph.cs
index eadaea099..7a984937b 100644
--- a/MelonLoader/InternalUtils/DependencyGraph.cs
+++ b/MelonLoader/InternalUtils/DependencyGraph.cs
@@ -188,6 +188,7 @@ private static string BuildMissingDependencyMessage(IDictionary !MelonUtils.IsGameIl2Cpp());
+
+    internal static bool Run()
     {
-        private static readonly string modulePath = Path.Combine(MelonEnvironment.Il2CppAssemblyGeneratorDirectory, "Il2CppAssemblyGenerator.dll");
-        public static readonly MelonModule.Info moduleInfo = new MelonModule.Info(modulePath, () => !MelonUtils.IsGameIl2Cpp());
+        if (MelonEnvironment.IsMonoRuntime)
+            return true;
+
+        MelonLogger.MsgDirect("Loading Il2CppAssemblyGenerator...");
+        var module = MelonModule.Load(moduleInfo);
+        if (module == null)
+        {
+            if (File.Exists(modulePath))
+                MelonLogger.Error("Failed to Load Il2CppAssemblyGenerator!");
+            else
+                MelonLogger.Error("Il2CppAssemblyGenerator was Not Found!");
+            return false;
+        }
 
-        internal static bool Run()
+        if (MelonUtils.IsWindows)
         {
-            if (MelonEnvironment.IsMonoRuntime)
-                return true;
-
-            MelonLogger.MsgDirect("Loading Il2CppAssemblyGenerator...");
-            var module = MelonModule.Load(moduleInfo);
-            if (module == null)
-            {
-                if (File.Exists(modulePath))
-                    MelonLogger.Error("Failed to Load Il2CppAssemblyGenerator!");
-                else
-                    MelonLogger.Error("Il2CppAssemblyGenerator was Not Found!");
-                return false;
-            }
-
-            if (MelonUtils.IsWindows)
-            {
-                IntPtr windowHandle = Process.GetCurrentProcess().MainWindowHandle;
+            var windowHandle = Process.GetCurrentProcess().MainWindowHandle;
 
 #if WINDOWS
-                BootstrapInterop.DisableCloseButton(windowHandle);
+            BootstrapInterop.DisableCloseButton(windowHandle);
 #endif
-            }
+        }
+
+        var ret = module.SendMessage("Run");
 
-            var ret = module.SendMessage("Run");
-            
-            if (MelonUtils.IsWindows)
-            {
-                IntPtr windowHandle = Process.GetCurrentProcess().MainWindowHandle;
+        if (MelonUtils.IsWindows)
+        {
+            var windowHandle = Process.GetCurrentProcess().MainWindowHandle;
 
 #if WINDOWS
-                BootstrapInterop.EnableCloseButton(windowHandle);
+            BootstrapInterop.EnableCloseButton(windowHandle);
 #endif
-            }
-
-            return ret is 0;
         }
+
+        return ret is 0;
     }
 }
 
diff --git a/MelonLoader/InternalUtils/UnityInformationHandler.cs b/MelonLoader/InternalUtils/UnityInformationHandler.cs
index bb2175f44..c8b151335 100644
--- a/MelonLoader/InternalUtils/UnityInformationHandler.cs
+++ b/MelonLoader/InternalUtils/UnityInformationHandler.cs
@@ -35,6 +35,7 @@ private static UnityVersion TryParse(string version)
                 MelonLogger.Error(ex);
             returnval = UnityVersion.MinVersion;
         }
+
         return returnval;
     }
 
@@ -129,6 +130,7 @@ private static void ReadGameInfo(AssetsManager assetsManager, string gameDataPat
             if (MelonDebug.IsEnabled())
                 MelonLogger.Error(ex);
         }
+
         instance?.file.Close();
     }
 
diff --git a/MelonLoader/LemonArraySegment.cs b/MelonLoader/LemonArraySegment.cs
index 33656de08..3a2c37813 100644
--- a/MelonLoader/LemonArraySegment.cs
+++ b/MelonLoader/LemonArraySegment.cs
@@ -26,7 +26,7 @@ public class LemonArraySegment : IList, ICollection, IEnumerable, IE
     ///    is .
     public LemonArraySegment(T[] array)
     {
-        Array = array ?? throw new ArgumentNullException("array");
+        Array = array ?? throw new ArgumentNullException(nameof(array));
         Offset = 0;
         Count = array.Length;
     }
@@ -44,13 +44,13 @@ public LemonArraySegment(T[] array)
     public LemonArraySegment(T[] array, int offset, int count)
     {
         if (array == null)
-            throw new ArgumentNullException("array");
+            throw new ArgumentNullException(nameof(array));
 
         if (offset < 0)
-            throw new ArgumentOutOfRangeException("offset", "Non-negative number required.");
+            throw new ArgumentOutOfRangeException(nameof(offset), "Non-negative number required.");
 
         if (count < 0)
-            throw new ArgumentOutOfRangeException("count", "Non-negative number required.");
+            throw new ArgumentOutOfRangeException(nameof(count), "Non-negative number required.");
 
         if (array.Length - offset < count)
             throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.");
@@ -103,14 +103,14 @@ T IList.this[int index]
         {
             return Array == null
                 ? throw new InvalidOperationException("The underlying array is null.")
-                : index < 0 || index >= Count ? throw new ArgumentOutOfRangeException("index") : Array[Offset + index];
+                : index < 0 || index >= Count ? throw new ArgumentOutOfRangeException(nameof(index)) : Array[Offset + index];
         }
         set
         {
             if (Array == null)
                 throw new InvalidOperationException("The underlying array is null.");
             if (index < 0 || index >= Count)
-                throw new ArgumentOutOfRangeException("index");
+                throw new ArgumentOutOfRangeException(nameof(index));
             Array[Offset + index] = value;
         }
     }
@@ -189,6 +189,7 @@ public bool MoveNext()
                 _current++;
                 return _current < _end;
             }
+
             return false;
         }
 
diff --git a/MelonLoader/LemonEnumerator.cs b/MelonLoader/LemonEnumerator.cs
index 257032865..37be1791b 100644
--- a/MelonLoader/LemonEnumerator.cs
+++ b/MelonLoader/LemonEnumerator.cs
@@ -1,4 +1,5 @@
-using System.Collections;
+using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 
@@ -67,5 +68,7 @@ public void Dispose()
     {
         Reset();
         LemonPatch = null;
+
+        GC.SuppressFinalize(this);
     }
 }
\ No newline at end of file
diff --git a/MelonLoader/MelonAction.cs b/MelonLoader/MelonAction.cs
index 9cd49dcc0..20b33f435 100644
--- a/MelonLoader/MelonAction.cs
+++ b/MelonLoader/MelonAction.cs
@@ -30,6 +30,7 @@ internal static List> Get(T del, int priority = 0, bool unsubscri
 
             result.Add(new MelonAction((T)met, priority, unsubscribeOnFirstInvocation));
         }
+
         return result;
     }
 }
diff --git a/MelonLoader/MelonBase.cs b/MelonLoader/MelonBase.cs
index 629e30a7a..4ad4f5ff4 100644
--- a/MelonLoader/MelonBase.cs
+++ b/MelonLoader/MelonBase.cs
@@ -299,6 +299,7 @@ public Incompatibility[] FindIncompatiblities(MelonGameAttribute game, string pr
             if (!(SupportedDomain == null || SupportedDomain.IsCompatible(domain)))
                 result.Add(Incompatibility.Domain);
         }
+
         if (!(SupportedMLVersion == null || SupportedMLVersion.IsCompatible(mlVersion)))
             result.Add(Incompatibility.MLVersion);
         else
@@ -329,6 +330,7 @@ public static void PrintIncompatibilities(Incompatibility[] incompatibilities, M
             foreach (var g in melon.Games)
                 MelonLogger.MsgDirect($"    - '{g.Name}' by {g.Developer}");
         }
+
         if (incompatibilities.Contains(Incompatibility.GameVersion))
         {
             MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Game Versions:");
@@ -336,6 +338,7 @@ public static void PrintIncompatibilities(Incompatibility[] incompatibilities, M
             foreach (var g in melon.SupportedGameVersions)
                 MelonLogger.MsgDirect($"    - {g.Version}");
         }
+
         if (incompatibilities.Contains(Incompatibility.ProcessName))
         {
             MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Process Names:");
@@ -343,6 +346,7 @@ public static void PrintIncompatibilities(Incompatibility[] incompatibilities, M
             foreach (var p in melon.SupportedProcesses)
                 MelonLogger.MsgDirect($"    - '{p.EXE_Name}'");
         }
+
         if (incompatibilities.Contains(Incompatibility.Platform))
         {
             MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Platforms:");
@@ -350,16 +354,19 @@ public static void PrintIncompatibilities(Incompatibility[] incompatibilities, M
             foreach (var p in melon.SupportedPlatforms.Platforms)
                 MelonLogger.MsgDirect($"    - {p}");
         }
+
         if (incompatibilities.Contains(Incompatibility.Domain))
         {
             MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Domain:");
             MelonLogger.MsgDirect($"    - {melon.SupportedDomain.Domain}");
         }
+
         if (incompatibilities.Contains(Incompatibility.MLVersion))
         {
             MelonLogger.MsgDirect($"- {melon.Info.Name}  is only compatible with the following MelonLoader Versions:");
             MelonLogger.MsgDirect($"    - {melon.SupportedMLVersion.SemVer}{(melon.SupportedMLVersion.IsMinimum ? " or higher" : "")}");
         }
+
         if (incompatibilities.Contains(Incompatibility.MLBuild))
         {
             MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following MelonLoader Build Hash Codes:");
diff --git a/MelonLoader/MelonPreferences.cs b/MelonLoader/MelonPreferences.cs
index 802128192..8d3be4543 100644
--- a/MelonLoader/MelonPreferences.cs
+++ b/MelonLoader/MelonPreferences.cs
@@ -126,6 +126,7 @@ public static void Save()
                         currentFile.InsertIntoDocument(category.Identifier, entry.Identifier, entry.Save());
             }
         }
+
         if (ReflectiveCategories.Count > 0)
         {
             foreach (var category in ReflectiveCategories)
@@ -160,6 +161,7 @@ public static void Save()
                     file.WasError = true;
                     continue;
                 }
+
                 OnPreferencesSaved.Invoke(file.FilePath);
             }
         }
@@ -168,38 +170,38 @@ public static void Save()
     }
 
     public static MelonPreferences_Category CreateCategory(string identifier) => CreateCategory(identifier, null, false);
-    public static MelonPreferences_Category CreateCategory(string identifier, string display_name = null) => CreateCategory(identifier, display_name, false);
+    public static MelonPreferences_Category CreateCategory(string identifier, string displayName = null) => CreateCategory(identifier, displayName, false);
 
-    public static MelonPreferences_Category CreateCategory(string identifier, string display_name = null, bool is_hidden = false, bool should_save = true)
+    public static MelonPreferences_Category CreateCategory(string identifier, string displayName = null, bool isHidden = false, bool shouldSave = true)
     {
         if (string.IsNullOrEmpty(identifier))
             throw new Exception("identifier is null or empty when calling CreateCategory");
-        display_name ??= identifier;
+        displayName ??= identifier;
         var category = GetCategory(identifier);
-        return category ?? new MelonPreferences_Category(identifier, display_name, is_hidden);
+        return category ?? new MelonPreferences_Category(identifier, displayName, isHidden);
     }
 
-    public static MelonPreferences_ReflectiveCategory CreateCategory(string identifier, string display_name = null) where T : new() => MelonPreferences_ReflectiveCategory.Create(identifier, display_name);
+    public static MelonPreferences_ReflectiveCategory CreateCategory(string identifier, string displayName = null) where T : new() => MelonPreferences_ReflectiveCategory.Create(identifier, displayName);
 
     [Obsolete]
-    public static MelonPreferences_Entry CreateEntry(string category_identifier, string entry_identifier,
-        T default_value, string display_name, bool is_hidden)
-        => CreateEntry(category_identifier, entry_identifier, default_value, display_name, null, is_hidden, false, null);
+    public static MelonPreferences_Entry CreateEntry(string categoryIdentifier, string entryIdentifier,
+        T defaultValue, string displayName, bool isHidden)
+        => CreateEntry(categoryIdentifier, entryIdentifier, defaultValue, displayName, null, isHidden, false, null);
 
-    public static MelonPreferences_Entry CreateEntry(string category_identifier, string entry_identifier, T default_value,
-        string display_name = null, string description = null, bool is_hidden = false, bool dont_save_default = false,
+    public static MelonPreferences_Entry CreateEntry(string categoryIdentifier, string entryIdentifier, T defaultValue,
+        string displayName = null, string description = null, bool isHidden = false, bool dontSaveDefault = false,
         ValueValidator validator = null)
     {
-        if (string.IsNullOrEmpty(category_identifier))
+        if (string.IsNullOrEmpty(categoryIdentifier))
             throw new Exception("category_identifier is null or empty when calling CreateEntry");
 
-        if (string.IsNullOrEmpty(entry_identifier))
+        if (string.IsNullOrEmpty(entryIdentifier))
             throw new Exception("entry_identifier is null or empty when calling CreateEntry");
 
-        var category = GetCategory(entry_identifier);
-        category ??= CreateCategory(category_identifier);
+        var category = GetCategory(entryIdentifier);
+        category ??= CreateCategory(categoryIdentifier);
 
-        return category.CreateEntry(entry_identifier, default_value, display_name, description, is_hidden, dont_save_default, validator);
+        return category.CreateEntry(entryIdentifier, defaultValue, displayName, description, isHidden, dontSaveDefault, validator);
     }
 
     public static MelonPreferences_Category GetCategory(string identifier)
@@ -229,23 +231,23 @@ public static void SaveCategory(string identifier, bool printmsg = true)
         category?.SaveToFile(printmsg);
     }
 
-    public static MelonPreferences_Entry GetEntry(string category_identifier, string entry_identifier) => GetCategory(category_identifier)?.GetEntry(entry_identifier);
-    public static MelonPreferences_Entry GetEntry(string category_identifier, string entry_identifier) => GetCategory(category_identifier)?.GetEntry(entry_identifier);
-    public static bool HasEntry(string category_identifier, string entry_identifier) => GetEntry(category_identifier, entry_identifier) != null;
+    public static MelonPreferences_Entry GetEntry(string categoryIdentifier, string entryIdentifier) => GetCategory(categoryIdentifier)?.GetEntry(entryIdentifier);
+    public static MelonPreferences_Entry GetEntry(string categoryIdentifier, string entryIdentifier) => GetCategory(categoryIdentifier)?.GetEntry(entryIdentifier);
+    public static bool HasEntry(string categoryIdentifier, string entryIdentifier) => GetEntry(categoryIdentifier, entryIdentifier) != null;
 
-    public static void SetEntryValue(string category_identifier, string entry_identifier, T value)
+    public static void SetEntryValue(string categoryIdentifier, string entryIdentifier, T value)
     {
-        var entry = GetCategory(category_identifier)?.GetEntry(entry_identifier);
+        var entry = GetCategory(categoryIdentifier)?.GetEntry(entryIdentifier);
         if (entry != null)
             entry.Value = value;
     }
 
-    public static T GetEntryValue(string category_identifier, string entry_identifier)
+    public static T GetEntryValue(string categoryIdentifier, string entryIdentifier)
     {
-        var cat = GetCategory(category_identifier);
+        var cat = GetCategory(categoryIdentifier);
         if (cat == null)
             return default;
-        var entry = cat.GetEntry(entry_identifier);
+        var entry = cat.GetEntry(entryIdentifier);
         return entry == null ? default : entry.Value;
     }
 
@@ -332,6 +334,7 @@ internal static void LoadFileAndRefreshCategories(Preferences.IO.File file, bool
                     category.LoadDefaults();
                     continue;
                 }
+
                 category.Load(table);
             }
 
diff --git a/MelonLoader/MelonPreferences_Category.cs b/MelonLoader/MelonPreferences_Category.cs
index 314d2eec8..16e31c440 100644
--- a/MelonLoader/MelonPreferences_Category.cs
+++ b/MelonLoader/MelonPreferences_Category.cs
@@ -119,6 +119,7 @@ public void SetFilePath(string filepath, bool autoload, bool printmsg)
                 MelonPreferences.PrefFiles.Remove(oldfile);
             }
         }
+
         if (!string.IsNullOrEmpty(filepath) && !MelonPreferences.IsFilePathDefault(filepath))
         {
             File = MelonPreferences.GetPrefFileFromFilePath(filepath);
@@ -128,6 +129,7 @@ public void SetFilePath(string filepath, bool autoload, bool printmsg)
                 MelonPreferences.PrefFiles.Add(File);
             }
         }
+
         if (autoload)
             MelonPreferences.LoadFileAndRefreshCategories(File, printmsg);
     }
@@ -143,6 +145,7 @@ public void ResetFilePath()
             oldfile.FileWatcher.Destroy();
             MelonPreferences.PrefFiles.Remove(oldfile);
         }
+
         MelonPreferences.LoadFileAndRefreshCategories(MelonPreferences.DefaultFile);
     }
 
@@ -162,6 +165,7 @@ public void SaveToFile(bool printmsg = true)
             MelonLogger.Error($"Error while Saving Preferences to {currentfile.FilePath}: {ex}");
             currentfile.WasError = true;
         }
+
         if (printmsg)
             MelonLogger.Msg($"MelonPreferences Saved to {currentfile.FilePath}");
 
diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs
index 6794e3a8d..9ef6f6772 100644
--- a/MelonLoader/MelonUtils.cs
+++ b/MelonLoader/MelonUtils.cs
@@ -128,6 +128,7 @@ public static void SetCurrentDomainBaseDirectory(string dirpath, AppDomain domai
         {
             MelonLogger.Warning($"AppDomainSetup.ApplicationBase Exception: {ex}");
         }
+
         Directory.SetCurrentDirectory(dirpath);
     }
 
@@ -150,6 +151,7 @@ public static MelonBase GetMelonFromStackTrace(StackTrace st, bool allFrames = f
                 if (ret != null)
                     return ret;
             }
+
             return null;
 
         }
@@ -238,6 +240,7 @@ public static T ParseJSONStringtoStruct(string jsonstr)
             MelonLogger.Error($"Exception while Decoding JSON String to JSON Variant: {ex}");
             return default;
         }
+
         if (jsonarr == null)
             return default;
         T returnobj = default;
@@ -249,6 +252,7 @@ public static T ParseJSONStringtoStruct(string jsonstr)
         {
             MelonLogger.Error($"Exception while Converting JSON Variant to {typeof(T).Name}: {ex}");
         }
+
         return returnobj;
     }
 
@@ -333,6 +337,7 @@ public static Type GetValidType(this Assembly asm, string typeName, LemonFunc LoadMelons(List melonAssemblies)
                 // Log Failure
                 MelonLogger.Warning($"Failed to load Melon '{m.Info.Name}' from '{m.MelonAssembly.Location}': The given Melon is a {m.MelonTypeName} and cannot be loaded as a {MelonTypeBase.TypeName}. Make sure it's in the right folder.");
             }
+
         return loadedMelons;
     }
 
diff --git a/MelonLoader/Modules/MelonModule.cs b/MelonLoader/Modules/MelonModule.cs
index e896e7c20..55450daec 100644
--- a/MelonLoader/Modules/MelonModule.cs
+++ b/MelonLoader/Modules/MelonModule.cs
@@ -45,6 +45,7 @@ internal static MelonModule Load(Info moduleInfo)
             {
                 MelonLogger.Warning($"Failed to remove MelonModule '{moduleInfo.fullPath}':\n{ex}");
             }
+
             return null;
         }
 
diff --git a/MelonLoader/NativeUtils/NativeHooks.cs b/MelonLoader/NativeUtils/NativeHooks.cs
index 2e1ef0d47..dce9cf4c0 100644
--- a/MelonLoader/NativeUtils/NativeHooks.cs
+++ b/MelonLoader/NativeUtils/NativeHooks.cs
@@ -27,7 +27,7 @@ public IntPtr Target
         set
         {
             if (value == IntPtr.Zero)
-                throw new ArgumentNullException("value");
+                throw new ArgumentNullException(nameof(value));
 
             _targetHandle = value;
         }
@@ -43,7 +43,7 @@ public IntPtr Detour
         set
         {
             if (value == IntPtr.Zero)
-                throw new ArgumentNullException("value");
+                throw new ArgumentNullException(nameof(value));
 
             _detourHandle = value;
         }
@@ -82,10 +82,10 @@ public NativeHook() { }
     public NativeHook(IntPtr target, IntPtr detour)
     {
         if (target == IntPtr.Zero)
-            throw new ArgumentNullException("target");
+            throw new ArgumentNullException(nameof(target));
 
         if (detour == IntPtr.Zero)
-            throw new ArgumentNullException("detour");
+            throw new ArgumentNullException(nameof(detour));
 
         _targetHandle = target;
         _detourHandle = detour;
diff --git a/MelonLoader/Pastel/Pastel.cs b/MelonLoader/Pastel/Pastel.cs
index 532bc281e..6cb6f1db1 100644
--- a/MelonLoader/Pastel/Pastel.cs
+++ b/MelonLoader/Pastel/Pastel.cs
@@ -105,6 +105,7 @@ static ConsoleExtensions()
             Enable();
             return;
         }
+
         var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
 
         _ = GetConsoleMode(iStdOut, out var outConsoleMode)
diff --git a/MelonLoader/Preferences/IO/Watcher.cs b/MelonLoader/Preferences/IO/Watcher.cs
index 2471572f8..7f8415ba7 100644
--- a/MelonLoader/Preferences/IO/Watcher.cs
+++ b/MelonLoader/Preferences/IO/Watcher.cs
@@ -58,6 +58,7 @@ internal void Destroy()
             MelonLogger.Warning("FileSystemWatcher Exception: " + ex.ToString());
             ShouldDisableFileWatcherFunctionality = true;
         }
+
         FileWatcher = null;
     }
 
@@ -68,6 +69,7 @@ private void OnFileWatcherTriggered(object source, FileSystemEventArgs e)
             PrefFile.IsSaving = false;
             return;
         }
+
         MelonPreferences.LoadFileAndRefreshCategories(PrefFile);
     }
 }
diff --git a/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs b/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs
index 2dc3e148c..10fee5704 100644
--- a/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs
+++ b/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs
@@ -84,6 +84,7 @@ public void SetFilePath(string filepath, bool autoload = true, bool printmsg = t
                 MelonPreferences.PrefFiles.Remove(oldfile);
             }
         }
+
         if (!string.IsNullOrEmpty(filepath) && !MelonPreferences.IsFilePathDefault(filepath))
         {
             File = MelonPreferences.GetPrefFileFromFilePath(filepath);
@@ -93,6 +94,7 @@ public void SetFilePath(string filepath, bool autoload = true, bool printmsg = t
                 MelonPreferences.PrefFiles.Add(File);
             }
         }
+
         if (autoload)
             MelonPreferences.LoadFileAndRefreshCategories(File, printmsg);
     }
@@ -108,6 +110,7 @@ public void ResetFilePath()
             oldfile.FileWatcher.Destroy();
             MelonPreferences.PrefFiles.Remove(oldfile);
         }
+
         MelonPreferences.LoadFileAndRefreshCategories(MelonPreferences.DefaultFile);
     }
 
@@ -126,6 +129,7 @@ public void SaveToFile(bool printmsg = true)
             MelonLogger.Error($"Error while Saving Preferences to {currentfile.FilePath}: {ex}");
             currentfile.WasError = true;
         }
+
         if (printmsg)
             MelonLogger.Msg($"MelonPreferences Saved to {currentfile.FilePath}");
 
diff --git a/MelonLoader/RegisterTypeInIl2Cpp.cs b/MelonLoader/RegisterTypeInIl2Cpp.cs
index eca7ffc4e..f79dcddff 100644
--- a/MelonLoader/RegisterTypeInIl2Cpp.cs
+++ b/MelonLoader/RegisterTypeInIl2Cpp.cs
@@ -27,7 +27,7 @@ public static void RegisterAssembly(Assembly asm)
         }
 
         var typeTbl = asm.GetValidTypes();
-        if ((typeTbl == null) || (typeTbl.Count() <= 0))
+        if ((typeTbl == null) || (!typeTbl.Any()))
             return;
         foreach (var type in typeTbl)
         {
diff --git a/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs b/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs
index bb1f121d5..c1b9d1cc8 100644
--- a/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs
+++ b/MelonLoader/RegisterTypeInIl2CppWithInterfaces.cs
@@ -49,7 +49,7 @@ public static void RegisterAssembly(Assembly asm)
         }
 
         var typeTbl = asm.GetValidTypes();
-        if ((typeTbl == null) || (typeTbl.Count() <= 0))
+        if ((typeTbl == null) || (!typeTbl.Any()))
             return;
 
         foreach (var type in typeTbl)
diff --git a/MelonLoader/Semver/IntExtensions.cs b/MelonLoader/Semver/IntExtensions.cs
index 071a9131e..fba621a79 100644
--- a/MelonLoader/Semver/IntExtensions.cs
+++ b/MelonLoader/Semver/IntExtensions.cs
@@ -28,12 +28,6 @@ public static int Digits(this int n)
             return 3;
         if (n < 10_000)
             return 4;
-        if (n < 100_000)
-            return 5;
-        if (n < 1_000_000)
-            return 6;
-        if (n < 10_000_000)
-            return 7;
-        return n < 100_000_000 ? 8 : n < 1_000_000_000 ? 9 : 10;
+        return n < 100_000 ? 5 : n < 1_000_000 ? 6 : n < 10_000_000 ? 7 : n < 100_000_000 ? 8 : n < 1_000_000_000 ? 9 : 10;
     }
 }
diff --git a/MelonLoader/Semver/SemVersion.cs b/MelonLoader/Semver/SemVersion.cs
index 3523a5d5b..aafc4172f 100644
--- a/MelonLoader/Semver/SemVersion.cs
+++ b/MelonLoader/Semver/SemVersion.cs
@@ -189,7 +189,7 @@ public static bool TryParse(string version, out SemVersion semver, bool strict =
     ///  if the two values are equal, otherwise .
     public static bool Equals(SemVersion versionA, SemVersion versionB)
     {
-        return ReferenceEquals(versionA, versionB) || versionA is not null && versionB is not null && versionA.Equals(versionB);
+        return ReferenceEquals(versionA, versionB) || (versionA is not null && versionB is not null && versionA.Equals(versionB));
     }
 
     /// 
@@ -200,9 +200,7 @@ public static bool Equals(SemVersion versionA, SemVersion versionB)
     /// A signed number indicating the relative values of  and .
     public static int Compare(SemVersion versionA, SemVersion versionB)
     {
-        if (ReferenceEquals(versionA, versionB))
-            return 0;
-        return versionA is null ? -1 : versionB is null ? 1 : versionA.CompareTo(versionB);
+        return ReferenceEquals(versionA, versionB) ? 0 : versionA is null ? -1 : versionB is null ? 1 : versionA.CompareTo(versionB);
     }
 
     /// 
@@ -295,11 +293,13 @@ public override string ToString()
             version.Append('-');
             version.Append(Prerelease);
         }
+
         if (Build.Length > 0)
         {
             version.Append('+');
             version.Append(Build);
         }
+
         return version.ToString();
     }
 
diff --git a/MelonLoader/SupportModule.cs b/MelonLoader/SupportModule.cs
index 9338edae0..d00d026ca 100644
--- a/MelonLoader/SupportModule.cs
+++ b/MelonLoader/SupportModule.cs
@@ -67,6 +67,7 @@ internal static bool Setup()
             MelonLogger.Error("No Support Module Loaded!");
             return false;
         }
+
         return true;
     }
 
diff --git a/MelonLoader/TinyJSON/Decoder.cs b/MelonLoader/TinyJSON/Decoder.cs
index f0e41b264..ea28fd517 100644
--- a/MelonLoader/TinyJSON/Decoder.cs
+++ b/MelonLoader/TinyJSON/Decoder.cs
@@ -240,7 +240,7 @@ private Variant DecodeNumber()
 
     private void ConsumeWhiteSpace()
     {
-        while (whiteSpace.IndexOf(PeekChar) != -1)
+        while (whiteSpace.Contains(PeekChar))
         {
             json.Read();
 
@@ -274,7 +274,7 @@ private string NextWord
         {
             var word = new StringBuilder();
 
-            while (wordBreak.IndexOf(PeekChar) == -1)
+            while (!wordBreak.Contains(PeekChar))
             {
                 word.Append(NextChar);
 
diff --git a/MelonLoader/TinyJSON/Extensions.cs b/MelonLoader/TinyJSON/Extensions.cs
index 76c7f663d..dda6aae04 100644
--- a/MelonLoader/TinyJSON/Extensions.cs
+++ b/MelonLoader/TinyJSON/Extensions.cs
@@ -9,12 +9,12 @@ public static bool AnyOfType(this IEnumerable source, Type exp
     {
         if (source == null)
         {
-            throw new ArgumentNullException("source");
+            throw new ArgumentNullException(nameof(source));
         }
 
         if (expectedType == null)
         {
-            throw new ArgumentNullException("expectedType");
+            throw new ArgumentNullException(nameof(expectedType));
         }
 
         foreach (var item in source)
diff --git a/MelonLoader/TinyJSON/JSON.cs b/MelonLoader/TinyJSON/JSON.cs
index 16ca4bc75..af702c7e2 100644
--- a/MelonLoader/TinyJSON/JSON.cs
+++ b/MelonLoader/TinyJSON/JSON.cs
@@ -84,7 +84,7 @@ public static class JSON
 
     public static Variant Load(string json)
     {
-        return string.IsNullOrEmpty(json) ? throw new ArgumentNullException("json") : Decoder.Decode(json);
+        return string.IsNullOrEmpty(json) ? throw new ArgumentNullException(nameof(json)) : Decoder.Decode(json);
     }
 
     public static string Dump(object data)
@@ -127,6 +127,7 @@ public static void Populate(Variant data, T item) where T : class
         {
             throw new ArgumentNullException(nameof(item));
         }
+
         DecodeFields(data, ref item);
     }
 
diff --git a/MelonLoader/Utils/AssemblyVerifier.cs b/MelonLoader/Utils/AssemblyVerifier.cs
index cddaa4be5..2fcfbdceb 100644
--- a/MelonLoader/Utils/AssemblyVerifier.cs
+++ b/MelonLoader/Utils/AssemblyVerifier.cs
@@ -7,201 +7,203 @@
 using System.Linq;
 using System.Runtime.CompilerServices;
 
-namespace MelonLoader.Utils
+namespace MelonLoader.Utils;
+
+internal static class AssemblyVerifier
 {
-    internal static class AssemblyVerifier
-    {
 
-        private static HashSet AllowedSymbols = new()
+    private static readonly HashSet AllowedSymbols =
+    [
+        '_',
+        '<',
+        '>',
+        '`',
+        '.',
+        '=',
+        '-',
+        '|',
+        ',',
+        '[',
+        ']',
+        '$',
+        ':',
+        '@',
+        '(',
+        ')',
+        '?',
+        '{',
+        '}'
+    ];
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    internal static void EnsureInitialized()
+    {
+        _ = new List
         {
-            '_',
-            '<',
-            '>',
-            '`',
-            '.',
-            '=',
-            '-',
-            '|',
-            ',',
-            '[',
-            ']',
-            '$',
-            ':',
-            '@',
-            '(',
-            ')',
-            '?',
-            '{',
-            '}'
+            //Force load AsmResolver
+            new Constant(ElementType.Class, null),
+            typeof(AsmResolver.PE.File.PEFile)
         };
+    }
 
-        [MethodImpl(MethodImplOptions.NoInlining)]
-        internal static void EnsureInitialized()
+    private static bool IsNameValid(string name)
+    {
+        if (name is null)
+            return false;
+
+        foreach (var c in name)
         {
-            var dummyListToEnsureThisCodeDoesntGetNuked = new List();
+            var isOk = false;
+            isOk |= c is >= 'a' and <= 'z';
+            isOk |= c is >= 'A' and <= 'Z';
+            isOk |= c is >= '0' and <= '9';
+            isOk |= AllowedSymbols.Contains(c);
 
-            //Force load AsmResolver
-            dummyListToEnsureThisCodeDoesntGetNuked.Add(new Constant(ElementType.Class, null));
-            dummyListToEnsureThisCodeDoesntGetNuked.Add(typeof(AsmResolver.PE.File.PEFile));
+            if (!isOk)
+                return false;
         }
 
-        private static bool IsNameValid(string name)
-        {
-            if (name is null) 
-                return false;
+        return true;
+    }
 
-            foreach (char c in name)
-            {
-                bool isOk = false;
-                isOk |= c is >= 'a' and <= 'z';
-                isOk |= c is >= 'A' and <= 'Z';
-                isOk |= c is >= '0' and <= '9';
-                isOk |= AllowedSymbols.Contains(c);
+    private static void CountChars(string str, ref Dictionary map)
+    {
+        foreach (var c in str)
+        {
+            if (map.ContainsKey(c))
+                map[c]++;
+            else
+                map.Add(c, 1);
+        }
+    }
 
-                if (!isOk)
-                    return false;
-            }
+    internal static bool CheckAssembly(ModuleDefinition image)
+    {
+        // string imageName = image.Name;
 
-            return true;
-        }
+        var moduleCount = image.Assembly!.Modules.Count;
 
-        private static void CountChars(string str, ref Dictionary map)
+        if (moduleCount is not 1)
         {
-            foreach (char c in str)
-            {
-                if (map.ContainsKey(c))
-                    map[c]++;
-                else
-                    map.Add(c, 1);
-            }
+            //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Module Count!");
+            return false;
         }
 
-        internal static bool CheckAssembly(ModuleDefinition image)
-        {
-            // string imageName = image.Name;
+        var tableStream = image.DotNetDirectory!.Metadata!.GetStream();
+        var stringStream = image.DotNetDirectory.Metadata.GetStream();
 
-            var moduleCount = image.Assembly!.Modules.Count;
+        var allTypes = image.GetAllTypes().ToList();
+        var numTypeDefs = allTypes.Count;
 
-            if (moduleCount is not 1)
-            {
-                //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Module Count!");
-                return false;
-            }
-            var tableStream = image.DotNetDirectory!.Metadata!.GetStream();
-            var stringStream = image.DotNetDirectory.Metadata.GetStream();
+        var methodTable = (MetadataTable)tableStream.GetTable(TableIndex.Method);
+        var numMethodDefs = methodTable.Count;
 
-            var allTypes = image.GetAllTypes().ToList();
-            var numTypeDefs = allTypes.Count;
+        var symbolCounts = new Dictionary();
+        foreach (var type in allTypes)
+        {
+            var typeNsStr = type.Namespace;
+            var typeNameStr = type.Name;
 
-            var methodTable = (MetadataTable) tableStream.GetTable(TableIndex.Method);
-            var numMethodDefs = methodTable.Count;
+            var baseType = type.BaseType;
 
-            var symbolCounts = new Dictionary();
-            foreach (var type in allTypes)
+            if (baseType != null && baseType.IsTypeOf("System", "MulticastDelegate"))
             {
-                var typeNsStr = type.Namespace;
-                var typeNameStr = type.Name;
-
-                var baseType = type.BaseType;
-
-                if (baseType != null && baseType.IsTypeOf("System", "MulticastDelegate"))
-                {
-                    if (type.Fields.Count != 0)
-                    {
-                        //MelonDebug.Msg($"{type.FullName} inherits from MulticastDelegate and has fields!");
-                        return false;
-                    }
-                }
-
-                if ((string) typeNsStr != null && !IsNameValid(typeNsStr))
-                {
-                    //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Namespace String \"{typeNsStr ?? "null"}\"");
-                    return false;
-                }
-
-                if (!IsNameValid(typeNameStr))
+                if (type.Fields.Count != 0)
                 {
-                    //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Type Name String \"{typeNameStr ?? null}\"");
+                    //MelonDebug.Msg($"{type.FullName} inherits from MulticastDelegate and has fields!");
                     return false;
                 }
+            }
 
-                if (typeNameStr == "")
-                {
-                    if (type.Fields.Count + type.Methods.Count != 0)
-                    {
-                        //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Module with Fields or Methods!");
-                        return false;
-                    }
-                }
-
-                CountChars(typeNameStr, ref symbolCounts);
+            if ((string)typeNsStr != null && !IsNameValid(typeNsStr))
+            {
+                //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Namespace String \"{typeNsStr ?? "null"}\"");
+                return false;
             }
 
-            foreach(var method in methodTable)
+            if (!IsNameValid(typeNameStr))
             {
-                var methodName = stringStream.GetStringByIndex(method.Name);
+                //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Type Name String \"{typeNameStr ?? null}\"");
+                return false;
+            }
 
-                if(!IsNameValid(methodName))
+            if (typeNameStr == "")
+            {
+                if (type.Fields.Count + type.Methods.Count != 0)
                 {
-                    //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Method: {method.Name}!");
+                    //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Module with Fields or Methods!");
                     return false;
                 }
-
-                CountChars(methodName, ref symbolCounts);
             }
 
-            if (numTypeDefs + numMethodDefs < 25)
-            {
-                //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} has too few chars to check entropy");
-                return true;
-            }
-
-            var totalChars = symbolCounts.Aggregate(0.0, (current, pair) => current + pair.Value);
-
-            var totalEntropy = symbolCounts.Sum(pair => pair.Value * Math.Log2(pair.Value / totalChars));
+            CountChars(typeNameStr, ref symbolCounts);
+        }
 
-            totalEntropy /= -totalChars;
+        foreach (var method in methodTable)
+        {
+            var methodName = stringStream.GetStringByIndex(method.Name);
 
-            if (totalEntropy is < 4 or > 5.5)
+            if (!IsNameValid(methodName))
             {
-                //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Entropy: {totalEntropy}!");
+                //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Method: {method.Name}!");
                 return false;
             }
 
-            //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} passes");
+            CountChars(methodName, ref symbolCounts);
+        }
 
+        if (numTypeDefs + numMethodDefs < 25)
+        {
+            //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} has too few chars to check entropy");
             return true;
-
         }
 
-        internal static (bool, string) VerifyFile(string assemblyFile)
+        var totalChars = symbolCounts.Aggregate(0.0, (current, pair) => current + pair.Value);
+
+        var totalEntropy = symbolCounts.Sum(pair => pair.Value * Math.Log2(pair.Value / totalChars));
+
+        totalEntropy /= -totalChars;
+
+        if (totalEntropy is < 4 or > 5.5)
         {
-            if (assemblyFile is not null)
-            {
-                var module = ModuleDefinition.FromFile(assemblyFile);
+            //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} Has an Invalid Entropy: {totalEntropy}!");
+            return false;
+        }
 
-                var checkResult = CheckAssembly(module);
+        //MelonDebug.Msg($"[AssemblyVerifier] {image.Name} passes");
 
-                if (!checkResult)
-                    return (false, "Invalid assembly");
-            }
+        return true;
+
+    }
+
+    internal static (bool, string) VerifyFile(string assemblyFile)
+    {
+        if (assemblyFile is not null)
+        {
+            var module = ModuleDefinition.FromFile(assemblyFile);
+
+            var checkResult = CheckAssembly(module);
 
-            return (true, null);
+            if (!checkResult)
+                return (false, "Invalid assembly");
         }
 
-        internal static (bool, string) VerifyByteArray(byte[] rawAssembly)
+        return (true, null);
+    }
+
+    internal static (bool, string) VerifyByteArray(byte[] rawAssembly)
+    {
+        if (rawAssembly is not null)
         {
-            if (rawAssembly is not null)
-            {
-                var module = ModuleDefinition.FromBytes(rawAssembly);
+            var module = ModuleDefinition.FromBytes(rawAssembly);
 
-                var checkResult = CheckAssembly(module);
+            var checkResult = CheckAssembly(module);
 
-                if (!checkResult)
-                    return (false, "Invalid assembly");
-            }
-            return (true, null);
+            if (!checkResult)
+                return (false, "Invalid assembly");
         }
+
+        return (true, null);
     }
 }
 #endif
diff --git a/MelonLoader/Utils/SteamManifestReader.cs b/MelonLoader/Utils/SteamManifestReader.cs
index 200613a93..53a7e2069 100644
--- a/MelonLoader/Utils/SteamManifestReader.cs
+++ b/MelonLoader/Utils/SteamManifestReader.cs
@@ -60,6 +60,7 @@ private static string ReadAppManifestInstallDir(string appmanifestpath)
             output = match.Groups[1].Value;
             break;
         }
+
         return output;
     }
 
@@ -93,6 +94,7 @@ private static string ReadLibraryFolders(string appmanifestfilename, ref string
             steamappspath = steamappspath2;
             output = installdir;
         }
+
         return output;
     }
 
diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs
index d985afb87..60b2b4bc3 100644
--- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs
+++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs
@@ -52,9 +52,9 @@ public Object mainAsset
 
     public bool Contains(string name)
     {
-        if (bundleptr == IntPtr.Zero)
-            throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero");
-        return string.IsNullOrEmpty(name)
+        return bundleptr == IntPtr.Zero
+            ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero")
+            : string.IsNullOrEmpty(name)
             ? throw new ArgumentException("The input asset name cannot be null or empty.")
             : ContainsDelegateField == null
             ? throw new NullReferenceException("The ContainsDelegateField cannot be null.")
@@ -113,11 +113,11 @@ public Object LoadAsset(string name, Il2CppSystem.Type type)
 
     public IntPtr LoadAsset(string name, IntPtr typeptr)
     {
-        if (bundleptr == IntPtr.Zero)
-            throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero");
-        if (string.IsNullOrEmpty(name))
-            throw new ArgumentException("The input asset name cannot be null or empty.");
-        return typeptr == IntPtr.Zero
+        return bundleptr == IntPtr.Zero
+            ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero")
+            : string.IsNullOrEmpty(name)
+            ? throw new ArgumentException("The input asset name cannot be null or empty.")
+            : typeptr == IntPtr.Zero
             ? throw new NullReferenceException("The input type cannot be IntPtr.Zero")
             : LoadAsset_InternalDelegateField == null
             ? throw new NullReferenceException("The LoadAsset_InternalDelegateField cannot be null.")
@@ -144,11 +144,11 @@ public Il2CppAssetBundleRequest LoadAssetAsync(string name, Il2CppSystem.Type ty
 
     public IntPtr LoadAssetAsync(string name, IntPtr typeptr)
     {
-        if (bundleptr == IntPtr.Zero)
-            throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero");
-        if (string.IsNullOrEmpty(name))
-            throw new ArgumentException("The input asset name cannot be null or empty.");
-        return typeptr == IntPtr.Zero
+        return bundleptr == IntPtr.Zero
+            ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero")
+            : string.IsNullOrEmpty(name)
+            ? throw new ArgumentException("The input asset name cannot be null or empty.")
+            : typeptr == IntPtr.Zero
             ? throw new NullReferenceException("The input type cannot be IntPtr.Zero")
             : LoadAssetAsync_InternalDelegateField == null
             ? throw new NullReferenceException("The LoadAssetAsync_InternalDelegateField cannot be null.")
@@ -210,11 +210,11 @@ public Il2CppReferenceArray LoadAssetWithSubAssets(string name, Il2CppSy
 
     public IntPtr LoadAssetWithSubAssets(string name, IntPtr typeptr)
     {
-        if (bundleptr == IntPtr.Zero)
-            throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero");
-        if (string.IsNullOrEmpty(name))
-            throw new ArgumentException("The input asset name cannot be null or empty.");
-        return typeptr == IntPtr.Zero
+        return bundleptr == IntPtr.Zero
+            ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero")
+            : string.IsNullOrEmpty(name)
+            ? throw new ArgumentException("The input asset name cannot be null or empty.")
+            : typeptr == IntPtr.Zero
             ? throw new NullReferenceException("The input type cannot be IntPtr.Zero")
             : LoadAssetWithSubAssets_InternalDelegateField == null
             ? throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null.")
@@ -240,11 +240,11 @@ public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name, Il2CppS
 
     public IntPtr LoadAssetWithSubAssetsAsync(string name, IntPtr typeptr)
     {
-        if (bundleptr == IntPtr.Zero)
-            throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero");
-        if (string.IsNullOrEmpty(name))
-            throw new ArgumentException("The input asset name cannot be null or empty.");
-        return typeptr == IntPtr.Zero
+        return bundleptr == IntPtr.Zero
+            ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero")
+            : string.IsNullOrEmpty(name)
+            ? throw new ArgumentException("The input asset name cannot be null or empty.")
+            : typeptr == IntPtr.Zero
             ? throw new NullReferenceException("The input type cannot be IntPtr.Zero")
             : LoadAssetWithSubAssetsAsync_InternalDelegateField == null
             ? throw new NullReferenceException("The LoadAssetWithSubAssetsAsync_InternalDelegateField cannot be null.")
diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs
index ff210d22d..b5ca2be1e 100644
--- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs
+++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs
@@ -20,7 +20,7 @@ public Il2CppAssetBundle assetBundle
         [Il2CppInterop.Runtime.Attributes.HideFromIl2Cpp]
         get
         {
-            var ptr = get_assetBundleDelegateField(this.Pointer);
+            var ptr = get_assetBundleDelegateField(Pointer);
             return ptr == IntPtr.Zero ? null : new Il2CppAssetBundle(ptr);
         }
     }
@@ -45,7 +45,7 @@ public Object asset
     {
         get
         {
-            var ptr = get_assetDelegateField(this.Pointer);
+            var ptr = get_assetDelegateField(Pointer);
             return ptr == IntPtr.Zero ? null : new Object(ptr);
         }
     }
@@ -54,7 +54,7 @@ public Il2CppReferenceArray allAssets
     {
         get
         {
-            var ptr = get_allAssetsDelegateField(this.Pointer);
+            var ptr = get_allAssetsDelegateField(Pointer);
             return ptr == IntPtr.Zero ? null : new Il2CppReferenceArray(ptr);
         }
     }
diff --git a/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs b/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs
index eb5ebfd61..efea976b5 100644
--- a/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs
+++ b/UnityUtilities/UnityEngine.Il2CppImageConversionManager/Il2CppImageConversionManager.cs
@@ -67,9 +67,9 @@ public static Il2CppStructArray EncodeToEXR(Texture2D tex, Texture2D.EXRFl
 
     public static bool LoadImage(Texture2D tex, Il2CppStructArray data, bool markNonReadable)
     {
-        if (tex == null)
-            throw new ArgumentException("The texture cannot be null.");
-        return data == null
+        return tex == null
+            ? throw new ArgumentException("The texture cannot be null.")
+            : data == null
             ? throw new ArgumentException("The data cannot be null.")
             : LoadImageDelegateField == null
             ? throw new NullReferenceException("The LoadImageDelegateField cannot be null.")

From d3b80dd69f790d70d1bf42421dfd2b6a663412db Mon Sep 17 00:00:00 2001
From: slxdy 
Date: Wed, 22 Jan 2025 21:05:17 +0100
Subject: [PATCH 11/18] Fix SharpZipLib exception namespaces

---
 .../SharpZipLib/{Core/Exceptions => }/SharpZipBaseException.cs  | 2 +-
 .../{Core/Exceptions => }/StreamDecodingException.cs            | 2 +-
 .../{Core/Exceptions => }/StreamUnsupportedException.cs         | 2 +-
 .../{Core/Exceptions => }/UnexpectedEndOfStreamException.cs     | 2 +-
 .../{Core/Exceptions => }/ValueOutOfRangeException.cs           | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)
 rename MelonLoader/ICSharpCode/SharpZipLib/{Core/Exceptions => }/SharpZipBaseException.cs (96%)
 rename MelonLoader/ICSharpCode/SharpZipLib/{Core/Exceptions => }/StreamDecodingException.cs (96%)
 rename MelonLoader/ICSharpCode/SharpZipLib/{Core/Exceptions => }/StreamUnsupportedException.cs (96%)
 rename MelonLoader/ICSharpCode/SharpZipLib/{Core/Exceptions => }/UnexpectedEndOfStreamException.cs (96%)
 rename MelonLoader/ICSharpCode/SharpZipLib/{Core/Exceptions => }/ValueOutOfRangeException.cs (97%)

diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs b/MelonLoader/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
similarity index 96%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs
rename to MelonLoader/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
index 85b263d6e..3f2c05275 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/SharpZipBaseException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
+namespace MelonLoader.ICSharpCode.SharpZipLib;
 
 /// 
 /// SharpZipBaseException is the base exception class for SharpZipLib.
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs b/MelonLoader/ICSharpCode/SharpZipLib/StreamDecodingException.cs
similarity index 96%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs
rename to MelonLoader/ICSharpCode/SharpZipLib/StreamDecodingException.cs
index 85f79a3bf..60cb3ede7 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamDecodingException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/StreamDecodingException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
+namespace MelonLoader.ICSharpCode.SharpZipLib;
 
 /// 
 /// Indicates that an error occurred during decoding of a input stream due to corrupt
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs b/MelonLoader/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
similarity index 96%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs
rename to MelonLoader/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
index a74a77a7f..3c1214345 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/StreamUnsupportedException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
+namespace MelonLoader.ICSharpCode.SharpZipLib;
 
 /// 
 /// Indicates that the input stream could not decoded due to known library incompability or missing features
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs b/MelonLoader/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs
similarity index 96%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs
rename to MelonLoader/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs
index 063a41541..fe7cd0d4b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/UnexpectedEndOfStreamException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
+namespace MelonLoader.ICSharpCode.SharpZipLib;
 
 /// 
 /// Indicates that the input stream could not decoded due to the stream ending before enough data had been provided
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs b/MelonLoader/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs
similarity index 97%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
rename to MelonLoader/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs
index c056119fc..fcd7c9acd 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/Exceptions/ValueOutOfRangeException.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Runtime.Serialization;
 
-namespace MelonLoader.ICSharpCode.SharpZipLib.Core.Exceptions;
+namespace MelonLoader.ICSharpCode.SharpZipLib;
 
 /// 
 /// Indicates that a value was outside of the expected range when decoding an input stream

From 056c2fb70915851a2f0efc8e40df82b8e686c48c Mon Sep 17 00:00:00 2001
From: slxdy 
Date: Wed, 22 Jan 2025 21:25:10 +0100
Subject: [PATCH 12/18] Final VS cleanup

---
 Dependencies/SupportModules/Component.cs                   | 7 +++++++
 .../ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs    | 6 +++---
 MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs        | 2 +-
 .../ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs        | 4 ++--
 MelonLoader/InteropSupport.cs                              | 4 ++--
 MelonLoader/MelonAssembly.cs                               | 2 +-
 MelonLoader/MelonLaunchOptions.cs                          | 4 ++--
 MelonLoader/MelonProcessAttribute.cs                       | 2 +-
 MelonLoader/Semver/IntExtensions.cs                        | 4 +---
 MelonLoader/TinyJSON/Decoder.cs                            | 1 +
 10 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/Dependencies/SupportModules/Component.cs b/Dependencies/SupportModules/Component.cs
index 1481ccd42..0fde40938 100644
--- a/Dependencies/SupportModules/Component.cs
+++ b/Dependencies/SupportModules/Component.cs
@@ -1,6 +1,8 @@
 using System;
 using System.Reflection;
 using UnityEngine;
+using System.Diagnostics.CodeAnalysis;
+
 
 #if SM_Il2Cpp
 using Il2CppInterop.Runtime;
@@ -8,6 +10,9 @@
 
 namespace MelonLoader.Support;
 
+[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used by Unity through reflection")]
+[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used by Unity through reflection")]
+[SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "It is necessary")]
 internal class SM_Component : MonoBehaviour
 {
     private bool isQuitting;
@@ -19,6 +24,8 @@ internal class SM_Component : MonoBehaviour
 #if SM_Il2Cpp
     private delegate bool SetAsLastSiblingDelegate(IntPtr transformptr);
     private static readonly SetAsLastSiblingDelegate SetAsLastSiblingDelegateField;
+
+    [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "")]
     public SM_Component(IntPtr value) : base(value) { }
 #endif
 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs
index d4dced728..9db1f4ba7 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs
@@ -101,7 +101,7 @@ public string TransformDirectory(string name)
         {
             while (name.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
             {
-                name = name.Remove(name.Length - 1, 1);
+                name = name[..^1];
             }
         }
         else
@@ -190,13 +190,13 @@ public static string MakeValidName(string name, char replacement)
         // Drop any leading slashes.
         while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar))
         {
-            name = name.Remove(0, 1);
+            name = name[1..];
         }
 
         // Drop any trailing slashes.
         while ((name.Length > 0) && (name[^1] == Path.DirectorySeparatorChar))
         {
-            name = name.Remove(name.Length - 1, 1);
+            name = name[..^1];
         }
 
         // Convert consecutive \\ characters to \
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
index 68b7b7d40..2c892f27e 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs
@@ -1090,7 +1090,7 @@ public static string CleanName(string name)
 
         while ((name.Length > 0) && (name[0] == '/'))
         {
-            name = name.Remove(0, 1);
+            name = name[1..];
         }
 
         return name;
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs
index 190238295..a7c059f2b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs
+++ b/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs
@@ -182,7 +182,7 @@ public static bool IsValidName(string name, bool relaxed)
             result = relaxed
             ? name.IndexOfAny(InvalidEntryCharsRelaxed) < 0
             : (name.IndexOfAny(InvalidEntryChars) < 0) &&
-                (!name.StartsWith('/'));
+                (name.Length == 0 || name[0] != '/');
         }
 
         return result;
@@ -205,7 +205,7 @@ public static bool IsValidName(string name)
         var result =
             (name != null) &&
             (name.IndexOfAny(InvalidEntryChars) < 0) &&
-            (!name.StartsWith('/'))
+            (name.Length == 0 || name[0] != '/')
             ;
         return result;
     }
diff --git a/MelonLoader/InteropSupport.cs b/MelonLoader/InteropSupport.cs
index 80bea0b40..3fea56d95 100644
--- a/MelonLoader/InteropSupport.cs
+++ b/MelonLoader/InteropSupport.cs
@@ -8,8 +8,8 @@ public static class InteropSupport
     public interface Interface
     {
         bool IsInheritedFromIl2CppObjectBase(Type type);
-        public bool IsInjectedType(Type type);
-        public IntPtr GetClassPointerForType(Type type);
+        bool IsInjectedType(Type type);
+        IntPtr GetClassPointerForType(Type type);
         FieldInfo MethodBaseToIl2CppFieldInfo(MethodBase method);
         int? GetIl2CppMethodCallerCount(MethodBase method);
         void RegisterTypeInIl2CppDomain(Type type, bool logSuccess);
diff --git a/MelonLoader/MelonAssembly.cs b/MelonLoader/MelonAssembly.cs
index 6b3868f18..e5fb3c14b 100644
--- a/MelonLoader/MelonAssembly.cs
+++ b/MelonLoader/MelonAssembly.cs
@@ -144,7 +144,7 @@ public static MelonAssembly LoadMelonAssembly(string path, Assembly assembly, bo
 
         var shortPath = path;
         if (shortPath.StartsWith(MelonEnvironment.MelonBaseDirectory))
-            shortPath = "." + shortPath.Remove(0, MelonEnvironment.MelonBaseDirectory.Length);
+            shortPath = "." + shortPath[MelonEnvironment.MelonBaseDirectory.Length..];
 
         OnAssemblyResolving.Invoke(assembly);
         ma = new MelonAssembly(assembly, path);
diff --git a/MelonLoader/MelonLaunchOptions.cs b/MelonLoader/MelonLaunchOptions.cs
index 56c87b948..7f333e09c 100644
--- a/MelonLoader/MelonLaunchOptions.cs
+++ b/MelonLoader/MelonLaunchOptions.cs
@@ -43,9 +43,9 @@ internal static void Load()
             // Parse Prefix
             var noPrefixCmd = fullcmd;
             if (noPrefixCmd.StartsWith("--"))
-                noPrefixCmd = noPrefixCmd.Remove(0, 2);
+                noPrefixCmd = noPrefixCmd[2..];
             else if (noPrefixCmd.StartsWith("-"))
-                noPrefixCmd = noPrefixCmd.Remove(0, 1);
+                noPrefixCmd = noPrefixCmd[1..];
             else
             {
                 // Unknown Command, Add it to Dictionary
diff --git a/MelonLoader/MelonProcessAttribute.cs b/MelonLoader/MelonProcessAttribute.cs
index 51444b2d3..36ac3ab35 100644
--- a/MelonLoader/MelonProcessAttribute.cs
+++ b/MelonLoader/MelonProcessAttribute.cs
@@ -26,6 +26,6 @@ public bool IsCompatible(string processName)
         => Universal || string.IsNullOrEmpty(processName) || (RemoveExtension(processName) == EXE_Name);
 
     private string RemoveExtension(string name)
-        => name == null ? null : (name.EndsWith(".exe") ? name.Remove(name.Length - 4) : name);
+        => name == null ? null : (name.EndsWith(".exe") ? name[..^4] : name);
 
 }
\ No newline at end of file
diff --git a/MelonLoader/Semver/IntExtensions.cs b/MelonLoader/Semver/IntExtensions.cs
index fba621a79..86e22be7b 100644
--- a/MelonLoader/Semver/IntExtensions.cs
+++ b/MelonLoader/Semver/IntExtensions.cs
@@ -26,8 +26,6 @@ public static int Digits(this int n)
             return 2;
         if (n < 1_000)
             return 3;
-        if (n < 10_000)
-            return 4;
-        return n < 100_000 ? 5 : n < 1_000_000 ? 6 : n < 10_000_000 ? 7 : n < 100_000_000 ? 8 : n < 1_000_000_000 ? 9 : 10;
+        return n < 10_000 ? 4 : n < 100_000 ? 5 : n < 1_000_000 ? 6 : n < 10_000_000 ? 7 : n < 100_000_000 ? 8 : n < 1_000_000_000 ? 9 : 10;
     }
 }
diff --git a/MelonLoader/TinyJSON/Decoder.cs b/MelonLoader/TinyJSON/Decoder.cs
index ea28fd517..9f563dcba 100644
--- a/MelonLoader/TinyJSON/Decoder.cs
+++ b/MelonLoader/TinyJSON/Decoder.cs
@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+using System.Linq;
 using System.Text;
 
 namespace MelonLoader.TinyJSON;

From a9478dd7c49233f9c39f9022bd4d823553f33610 Mon Sep 17 00:00:00 2001
From: slxdy 
Date: Wed, 22 Jan 2025 22:13:10 +0100
Subject: [PATCH 13/18] Deprecate TinyJSON and SharpZipLib

---
 .editorconfig                                 |  3 ++
 .../Il2CppAssemblyGenerator.csproj            |  1 +
 .../Il2CppAssemblyGenerator/RemoteAPI.cs      | 34 +++++----------
 Dependencies/SupportModules/Component.cs      |  2 -
 Dependencies/SupportModules/Il2Cpp/Main.cs    |  5 ++-
 Dependencies/SupportModules/Mono/Main.cs      |  7 ++--
 .../SupportModules/SupportModule_To.cs        |  5 ++-
 MelonLoader/Assertions/LemonAssertMapping.cs  | 27 +++++++++---
 .../BackwardsCompatibility/.editorconfig      |  3 +-
 .../ICSharpCode/SharpZipLib/BZip2/BZip2.cs    |  1 +
 .../SharpZipLib/BZip2/BZip2Constants.cs       |  3 ++
 .../SharpZipLib/BZip2/BZip2Exception.cs       |  1 +
 .../SharpZipLib/BZip2/BZip2InputStream.cs     |  1 +
 .../SharpZipLib/BZip2/BZip2OutputStream.cs    |  1 +
 .../SharpZipLib/Checksum/Adler32.cs           |  1 +
 .../SharpZipLib/Checksum/BZip2Crc.cs          |  1 +
 .../ICSharpCode/SharpZipLib/Checksum/Crc32.cs |  1 +
 .../SharpZipLib/Checksum/CrcUtilities.cs      |  5 ++-
 .../SharpZipLib/Checksum/IChecksum.cs         |  1 +
 .../ICSharpCode/SharpZipLib/Core/EmptyRefs.cs |  5 ++-
 .../SharpZipLib/Core/FileSystemScanner.cs     | 10 +++++
 .../SharpZipLib/Core/INameTransform.cs        |  3 ++
 .../SharpZipLib/Core/IScanFilter.cs           |  5 ++-
 .../SharpZipLib/Core/InvalidNameException.cs  |  1 +
 .../SharpZipLib/Core/NameFilter.cs            |  1 +
 .../SharpZipLib/Core/PathFilter.cs            |  4 +-
 .../ICSharpCode/SharpZipLib/Core/PathUtils.cs |  1 +
 .../SharpZipLib/Core/StreamUtils.cs           |  1 +
 .../SharpZipLib/Encryption/PkzipClassic.cs    |  5 +++
 .../SharpZipLib/Encryption/ZipAESStream.cs    |  1 +
 .../SharpZipLib/Encryption/ZipAESTransform.cs |  1 +
 .../SharpZipLib/GZip/GZIPConstants.cs         |  2 +
 .../ICSharpCode/SharpZipLib/GZip/GZip.cs      |  7 ++--
 .../SharpZipLib/GZip/GZipException.cs         |  1 +
 .../SharpZipLib/GZip/GzipInputStream.cs       |  1 +
 .../SharpZipLib/GZip/GzipOutputStream.cs      |  1 +
 .../ICSharpCode/SharpZipLib/LICENSE.txt       |  0
 .../SharpZipLib/Lzw/LzwConstants.cs           |  3 ++
 .../SharpZipLib/Lzw/LzwException.cs           |  1 +
 .../SharpZipLib/Lzw/LzwInputStream.cs         |  1 +
 .../ICSharpCode/SharpZipLib/README.md         |  0
 .../SharpZipLib/SharpZipBaseException.cs      |  1 +
 .../SharpZipLib/StreamDecodingException.cs    |  1 +
 .../SharpZipLib/StreamUnsupportedException.cs |  1 +
 .../SharpZipLib/Tar/InvalidHeaderException.cs |  1 +
 .../ICSharpCode/SharpZipLib/Tar/TarArchive.cs |  2 +
 .../ICSharpCode/SharpZipLib/Tar/TarBuffer.cs  |  1 +
 .../ICSharpCode/SharpZipLib/Tar/TarEntry.cs   |  1 +
 .../SharpZipLib/Tar/TarException.cs           |  1 +
 .../Tar/TarExtendedHeaderReader.cs            |  4 +-
 .../ICSharpCode/SharpZipLib/Tar/TarHeader.cs  |  1 +
 .../SharpZipLib/Tar/TarInputStream.cs         |  1 +
 .../SharpZipLib/Tar/TarOutputStream.cs        |  1 +
 .../UnexpectedEndOfStreamException.cs         |  1 +
 .../SharpZipLib/ValueOutOfRangeException.cs   |  1 +
 .../SharpZipLib/Zip/Compression/Deflater.cs   |  1 +
 .../Zip/Compression/DeflaterConstants.cs      |  1 +
 .../Zip/Compression/DeflaterEngine.cs         |  2 +
 .../Zip/Compression/DeflaterHuffman.cs        |  1 +
 .../Zip/Compression/DeflaterPending.cs        |  3 ++
 .../SharpZipLib/Zip/Compression/Inflater.cs   |  1 +
 .../Zip/Compression/InflaterDynHeader.cs      |  2 +
 .../Zip/Compression/InflaterHuffmanTree.cs    |  1 +
 .../Zip/Compression/PendingBuffer.cs          |  3 ++
 .../Streams/DeflaterOutputStream.cs           |  1 +
 .../Streams/InflaterInputStream.cs            |  2 +
 .../Zip/Compression/Streams/OutputWindow.cs   |  1 +
 .../Compression/Streams/StreamManipulator.cs  |  1 +
 .../ICSharpCode/SharpZipLib/Zip/FastZip.cs    | 42 +++++++++----------
 .../SharpZipLib/Zip/IEntryFactory.cs          |  8 ++--
 .../SharpZipLib/Zip/WindowsNameTransform.cs   |  1 +
 .../SharpZipLib/Zip/ZipConstants.cs           |  5 +++
 .../SharpZipLib/Zip/ZipEncryptionMethod.cs    |  5 ++-
 .../ICSharpCode/SharpZipLib/Zip/ZipEntry.cs   |  2 +
 .../SharpZipLib/Zip/ZipEntryExtensions.cs     |  5 ++-
 .../SharpZipLib/Zip/ZipEntryFactory.cs        |  1 +
 .../SharpZipLib/Zip/ZipException.cs           |  1 +
 .../SharpZipLib/Zip/ZipExtraData.cs           |  6 +++
 .../ICSharpCode/SharpZipLib/Zip/ZipFile.cs    | 18 +++++++-
 .../SharpZipLib/Zip/ZipHelperStream.cs        |  3 ++
 .../SharpZipLib/Zip/ZipInputStream.cs         |  1 +
 .../SharpZipLib/Zip/ZipNameTransform.cs       |  2 +
 .../SharpZipLib/Zip/ZipOutputStream.cs        |  1 +
 .../ICSharpCode/SharpZipLib/Zip/ZipStrings.cs |  1 +
 .../ISupportModule_From.cs                    |  7 +++-
 .../ISupportModule_To.cs                      | 12 ++++++
 .../TinyJSON/Decoder.cs                       |  1 +
 .../TinyJSON/EncodeOptions.cs                 |  1 +
 .../TinyJSON/Encoder.cs                       |  1 +
 .../TinyJSON/Extensions.cs                    |  1 +
 .../TinyJSON/JSON.cs                          | 12 +++++-
 .../TinyJSON/LICENSE.md                       |  0
 .../TinyJSON/ProxyArray.cs                    |  2 +
 .../TinyJSON/ProxyBoolean.cs                  |  1 +
 .../TinyJSON/ProxyNumber.cs                   |  1 +
 .../TinyJSON/ProxyObject.cs                   |  2 +
 .../TinyJSON/ProxyString.cs                   |  1 +
 .../TinyJSON/README.md                        |  0
 .../TinyJSON/Variant.cs                       |  1 +
 MelonLoader/MelonAssembly.cs                  | 12 +++---
 MelonLoader/MelonBase.cs                      |  2 +
 MelonLoader/MelonLoader.csproj                |  1 +
 MelonLoader/MelonUtils.cs                     |  1 +
 MelonLoader/Modules/ISupportModuleFrom.cs     | 16 +++++++
 .../ISupportModuleTo.cs}                      |  4 +-
 MelonLoader/SupportModule.cs                  |  7 ++--
 MelonLoader/SupportModule_From.cs             |  6 ++-
 107 files changed, 298 insertions(+), 92 deletions(-)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/BZip2/BZip2.cs (96%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs (97%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Checksum/Adler32.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Checksum/Crc32.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs (97%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs (92%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs (52%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs (93%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/INameTransform.cs (85%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/IScanFilter.cs (68%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/NameFilter.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/PathFilter.cs (96%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/PathUtils.cs (95%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Core/StreamUtils.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs (96%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs (89%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/GZip/GZip.cs (93%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/GZip/GZipException.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/LICENSE.txt (100%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs (92%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Lzw/LzwException.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/README.md (100%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/SharpZipBaseException.cs (95%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/StreamDecodingException.cs (95%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs (95%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarArchive.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarEntry.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarException.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarHeader.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs (95%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs (96%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs (97%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs (78%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/FastZip.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs (91%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs (96%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs (73%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs (85%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipException.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs (97%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipFile.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs (97%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/ISupportModule_From.cs (84%)
 create mode 100644 MelonLoader/BackwardsCompatibility/ISupportModule_To.cs
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/Decoder.cs (98%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/EncodeOptions.cs (73%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/Encoder.cs (99%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/Extensions.cs (84%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/JSON.cs (94%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/LICENSE.md (100%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/ProxyArray.cs (92%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/ProxyBoolean.cs (77%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/ProxyNumber.cs (96%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/ProxyObject.cs (92%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/ProxyString.cs (72%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/README.md (100%)
 rename MelonLoader/{ => BackwardsCompatibility}/TinyJSON/Variant.cs (97%)
 create mode 100644 MelonLoader/Modules/ISupportModuleFrom.cs
 rename MelonLoader/{ISupportModule_To.cs => Modules/ISupportModuleTo.cs} (71%)

diff --git a/.editorconfig b/.editorconfig
index 31946e2e8..3ac57d57b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -59,6 +59,9 @@ dotnet_diagnostic.CA1845.severity = warning
 csharp_preserve_single_line_statements = false
 csharp_preserve_single_line_blocks = true
 
+# IDE0079: Remove unnecessary suppression
+dotnet_diagnostic.IDE0079.severity = none
+
 [*.{cs,vb}]
 #### Naming styles ####
 
diff --git a/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj b/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj
index 61168b91a..b39fded0a 100644
--- a/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj
+++ b/Dependencies/Il2CppAssemblyGenerator/Il2CppAssemblyGenerator.csproj
@@ -17,5 +17,6 @@
         
         
         
+        
     
 
\ No newline at end of file
diff --git a/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs b/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs
index 00798b242..1ebd2b81d 100644
--- a/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs
+++ b/Dependencies/Il2CppAssemblyGenerator/RemoteAPI.cs
@@ -1,4 +1,5 @@
-using Semver;
+using Newtonsoft.Json;
+using Semver;
 using System;
 using System.Collections.Generic;
 using System.Net.Http;
@@ -12,9 +13,16 @@ internal static class RemoteAPI
 {
     internal class InfoStruct
     {
+        [JsonProperty("forceCpp2IlVersion")]
         internal string ForceDumperVersion = null;
+
+        [JsonProperty("obfuscationRegex")]
         internal string ObfuscationRegex = null;
+
+        [JsonProperty("mappingUrl")]
         internal string MappingURL = null;
+
+        [JsonProperty("mappingFileSHA512")]
         internal string MappingFileSHA512 = null;
     }
     internal static InfoStruct Info = new();
@@ -122,29 +130,7 @@ internal static class Melon
 
             internal static InfoStruct Contact(string response_str)
             {
-                var responseobj = MelonUtils.ParseJSONStringtoStruct(response_str);
-                if (responseobj == null)
-                    return null;
-
-                var returninfo = new InfoStruct
-                {
-                    ForceDumperVersion = responseobj.forceCpp2IlVersion,
-                    ObfuscationRegex = responseobj.obfuscationRegex,
-                    MappingURL = responseobj.mappingUrl,
-                    MappingFileSHA512 = responseobj.mappingFileSHA512
-                };
-                return returninfo;
-            }
-
-            internal class ResponseStruct
-            {
-                public string gameSlug = null;
-                public string gameName = null;
-                public string mappingUrl = null;
-                public string mappingFileSHA512 = null;
-                public string forceCpp2IlVersion = null;
-                public string forceUnhollowerVersion = null; //TODO: Remove this from the API
-                public string obfuscationRegex = null;
+                return JsonConvert.DeserializeObject(response_str);
             }
         }
     }
diff --git a/Dependencies/SupportModules/Component.cs b/Dependencies/SupportModules/Component.cs
index 0fde40938..06e73d820 100644
--- a/Dependencies/SupportModules/Component.cs
+++ b/Dependencies/SupportModules/Component.cs
@@ -3,7 +3,6 @@
 using UnityEngine;
 using System.Diagnostics.CodeAnalysis;
 
-
 #if SM_Il2Cpp
 using Il2CppInterop.Runtime;
 #endif
@@ -12,7 +11,6 @@ namespace MelonLoader.Support;
 
 [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used by Unity through reflection")]
 [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used by Unity through reflection")]
-[SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "It is necessary")]
 internal class SM_Component : MonoBehaviour
 {
     private bool isQuitting;
diff --git a/Dependencies/SupportModules/Il2Cpp/Main.cs b/Dependencies/SupportModules/Il2Cpp/Main.cs
index 4f3a01ccd..5047eaf18 100644
--- a/Dependencies/SupportModules/Il2Cpp/Main.cs
+++ b/Dependencies/SupportModules/Il2Cpp/Main.cs
@@ -3,6 +3,7 @@
 using Il2CppInterop.Runtime.Injection;
 using Il2CppInterop.Runtime.Startup;
 using MelonLoader.CoreClrUtils;
+using MelonLoader.Modules;
 using MelonLoader.Support.Preferences;
 using MelonLoader.Utils;
 using Microsoft.Extensions.Logging;
@@ -20,7 +21,7 @@ namespace MelonLoader.Support;
 
 internal static class Main
 {
-    internal static ISupportModule_From Interface;
+    internal static ISupportModuleFrom Interface;
     internal static InteropInterface Interop;
     internal static GameObject obj = null;
     internal static SM_Component component = null;
@@ -28,7 +29,7 @@ internal static class Main
     private static Assembly Il2Cppmscorlib = null;
     private static Type streamType = null;
 
-    private static ISupportModule_To Initialize(ISupportModule_From interface_from)
+    private static ISupportModuleTo Initialize(ISupportModuleFrom interface_from)
     {
         Interface = interface_from;
 
diff --git a/Dependencies/SupportModules/Mono/Main.cs b/Dependencies/SupportModules/Mono/Main.cs
index f1d63e7b9..fe1ab009e 100644
--- a/Dependencies/SupportModules/Mono/Main.cs
+++ b/Dependencies/SupportModules/Mono/Main.cs
@@ -1,4 +1,5 @@
-using MelonLoader.Support.Preferences;
+using MelonLoader.Modules;
+using MelonLoader.Support.Preferences;
 using System.Reflection;
 using UnityEngine;
 
@@ -8,11 +9,11 @@ namespace MelonLoader.Support;
 
 internal static class Main
 {
-    internal static ISupportModule_From Interface = null;
+    internal static ISupportModuleFrom Interface = null;
     internal static GameObject obj = null;
     internal static SM_Component component = null;
 
-    private static ISupportModule_To Initialize(ISupportModule_From interface_from)
+    private static ISupportModuleTo Initialize(ISupportModuleFrom interface_from)
     {
         Interface = interface_from;
         UnityMappers.RegisterMappers();
diff --git a/Dependencies/SupportModules/SupportModule_To.cs b/Dependencies/SupportModules/SupportModule_To.cs
index d6cef384d..8e09e405e 100644
--- a/Dependencies/SupportModules/SupportModule_To.cs
+++ b/Dependencies/SupportModules/SupportModule_To.cs
@@ -1,10 +1,11 @@
-using System.Collections;
+using MelonLoader.Modules;
+using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 namespace MelonLoader.Support;
 
-internal class SupportModule_To : ISupportModule_To
+internal class SupportModule_To : ISupportModuleTo
 {
     internal static readonly List QueuedCoroutines = [];
     public object StartCoroutine(IEnumerator coroutine)
diff --git a/MelonLoader/Assertions/LemonAssertMapping.cs b/MelonLoader/Assertions/LemonAssertMapping.cs
index c98b19336..768fd4bf3 100644
--- a/MelonLoader/Assertions/LemonAssertMapping.cs
+++ b/MelonLoader/Assertions/LemonAssertMapping.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 
 namespace MelonLoader.Assertions;
 
@@ -10,15 +11,17 @@ public static class LemonAssertMapping
 
     internal static void Setup()
     {
-        Register_IsNull(IsNull_object);
-        Register_IsNull(IsNull_string);
-        Register_IsEqual(IsEqual_object);
+        RegisterIsNull(IsNull_object);
+        RegisterIsNull(IsNull_string);
+        RegisterIsEqual(IsEqual_object);
     }
 
-    public static void Register_IsNull(Func method)
+    public static void RegisterIsNull(Func method)
         => Register(method, ref IsNull);
-    public static void Register_IsEqual(Func method)
+
+    public static void RegisterIsEqual(Func method)
         => Register(method, ref IsEqual);
+
     private static void Register(Delegate method, ref Dictionary tbl)
     {
         if (method == null)
@@ -38,4 +41,18 @@ private static bool IsEqual_object(object obj, object obj2)
     {
         return obj == null ? obj2 == null : obj2 == null ? obj == null : obj.Equals(obj2);
     }
+
+    #region Obsolete
+
+    [Obsolete("Use RegisterIsNull instead.", true)]
+    [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "Reason for deprecation")]
+    public static void Register_IsNull(Func method)
+        => Register(method, ref IsNull);
+
+    [Obsolete("Use RegisterIsNull instead.", true)]
+    [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "Reason for deprecation")]
+    public static void Register_IsEqual(Func method)
+        => Register(method, ref IsEqual);
+
+    #endregion
 }
diff --git a/MelonLoader/BackwardsCompatibility/.editorconfig b/MelonLoader/BackwardsCompatibility/.editorconfig
index a06223b95..479876501 100644
--- a/MelonLoader/BackwardsCompatibility/.editorconfig
+++ b/MelonLoader/BackwardsCompatibility/.editorconfig
@@ -1,2 +1,3 @@
 [*.cs]
-dotnet_style_namespace_match_folder = false
\ No newline at end of file
+dotnet_style_namespace_match_folder = false
+dotnet_diagnostic.CA1707.severity = none
\ No newline at end of file
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2.cs
similarity index 96%
rename from MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2.cs
index a110d7a69..3928b9494 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2;
 /// 
 /// An example class to demonstrate compression and decompression of BZip2 streams.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public static class BZip2
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs
similarity index 97%
rename from MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs
index ccd4529bb..e4571a47e 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2Constants.cs
@@ -1,8 +1,11 @@
+using System;
+
 namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2;
 
 /// 
 /// Defines internal values for both compression and decompression
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal static class BZip2Constants
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs
similarity index 94%
rename from MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs
index 7d7f70896..2f4acf3c6 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2Exception.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2;
 /// 
 /// BZip2Exception represents exceptions specific to BZip2 classes and code.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class BZip2Exception : SharpZipBaseException
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
similarity index 99%
rename from MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
index 1aa15ebf8..c3d3be656 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2InputStream.cs
@@ -11,6 +11,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2;
 /// 
 /// An input stream that decompresses files in the BZip2 format
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class BZip2InputStream : Stream
 {
     #region Constants
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
similarity index 99%
rename from MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
index 5aba054e1..f6e246410 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/BZip2/BZip2OutputStream.cs
@@ -8,6 +8,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.BZip2;
 /// An output stream that compresses into the BZip2 format
 /// including file header chars into another stream.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class BZip2OutputStream : Stream
 {
     #region Constants
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
index b01eaae25..d5f1042d2 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/Adler32.cs
@@ -48,6 +48,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum;
 /// 
 /// 
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public sealed class Adler32 : IChecksum
 {
     #region Instance Fields
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs
index 1d4063900..c49e8faee 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/BZip2Crc.cs
@@ -40,6 +40,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum;
 /// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table,
 /// which is still small enough to fit in most processors' L1 cache.)
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public sealed class BZip2Crc : IChecksum
 {
     #region Instance Fields
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/Crc32.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/Crc32.cs
index f167bc770..9baa63e57 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/Crc32.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/Crc32.cs
@@ -40,6 +40,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum;
 /// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table,
 /// which is still small enough to fit in most processors' L1 cache.)
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public sealed class Crc32 : IChecksum
 {
     #region Instance Fields
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs
similarity index 97%
rename from MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs
index bbea95186..4867c89c8 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/CrcUtilities.cs
@@ -1,5 +1,8 @@
-namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum;
+using System;
 
+namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum;
+
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal static class CrcUtilities
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs
similarity index 92%
rename from MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs
index e6baae37b..4169eec48 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Checksum/IChecksum.cs
@@ -9,6 +9,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Checksum;
 /// getValue. The complete checksum object can also be reset
 /// so it can be used again with new data.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public interface IChecksum
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs
similarity index 52%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs
index 8dce143f2..6611f0bce 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/EmptyRefs.cs
@@ -1,5 +1,8 @@
-namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
+using System;
 
+namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
+
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal static class Empty
 {
     internal static class EmptyArray
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
similarity index 93%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
index 90a01be3c..72053af6f 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/FileSystemScanner.cs
@@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 /// 
 /// Event arguments for scanning.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class ScanEventArgs : EventArgs
 {
     #region Constructors
@@ -50,6 +51,7 @@ public bool ContinueRunning
 /// 
 /// Event arguments during processing of a single file or directory.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class ProgressEventArgs : EventArgs
 {
     #region Constructors
@@ -129,6 +131,7 @@ public long Target
 /// 
 /// Event arguments for directories.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class DirectoryEventArgs : ScanEventArgs
 {
     #region Constructors
@@ -166,6 +169,7 @@ private readonly
 /// 
 /// Arguments passed when scan failures are detected.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class ScanFailureEventArgs : EventArgs
 {
     #region Constructors
@@ -227,6 +231,7 @@ public bool ContinueRunning
 /// 
 /// The source of the event
 /// The event arguments.
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public delegate void ProcessFileHandler(object sender, ScanEventArgs e);
 
 /// 
@@ -234,6 +239,7 @@ public bool ContinueRunning
 /// 
 /// The source of the event
 /// The event arguments.
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public delegate void ProgressHandler(object sender, ProgressEventArgs e);
 
 /// 
@@ -241,6 +247,7 @@ public bool ContinueRunning
 /// 
 /// The source of the event
 /// The event arguments.
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public delegate void CompletedFileHandler(object sender, ScanEventArgs e);
 
 /// 
@@ -248,6 +255,7 @@ public bool ContinueRunning
 /// 
 /// The source of the event
 /// The event arguments.
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public delegate void DirectoryFailureHandler(object sender, ScanFailureEventArgs e);
 
 /// 
@@ -255,6 +263,7 @@ public bool ContinueRunning
 /// 
 /// The source of the event
 /// The event arguments.
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public delegate void FileFailureHandler(object sender, ScanFailureEventArgs e);
 
 #endregion Delegates
@@ -262,6 +271,7 @@ public bool ContinueRunning
 /// 
 /// FileSystemScanner provides facilities scanning of files and directories.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class FileSystemScanner
 {
     #region Constructors
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/INameTransform.cs
similarity index 85%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/INameTransform.cs
index 8db45bb03..8a19d617f 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/INameTransform.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/INameTransform.cs
@@ -1,8 +1,11 @@
+using System;
+
 namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 
 /// 
 /// INameTransform defines how file system names are transformed for use with archives, or vice versa.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public interface INameTransform
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/IScanFilter.cs
similarity index 68%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/IScanFilter.cs
index 332ef67e0..1b107d4c0 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/IScanFilter.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/IScanFilter.cs
@@ -1,8 +1,11 @@
-namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
+using System;
+
+namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 
 /// 
 /// Scanning filters support filtering of names.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public interface IScanFilter
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs
similarity index 94%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs
index 6adcfe577..b18357e0d 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/InvalidNameException.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 /// 
 /// InvalidNameException is thrown for invalid names such as directory traversal paths and names with invalid characters
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class InvalidNameException : SharpZipBaseException
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/NameFilter.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/NameFilter.cs
index 81392aa46..6b53e2a4d 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/NameFilter.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/NameFilter.cs
@@ -19,6 +19,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 /// The following expression includes all name ending in '.dat' with the exception of 'dummy.dat'
 /// "+\.dat$;-^dummy\.dat$"
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class NameFilter : IScanFilter
 {
     #region Constructors
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/PathFilter.cs
similarity index 96%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/PathFilter.cs
index fae685367..21f8c0a91 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathFilter.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/PathFilter.cs
@@ -8,6 +8,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 /// by full path name.
 /// See NameFilter for more detail on filtering.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class PathFilter : IScanFilter
 {
     #region Constructors
@@ -59,6 +60,7 @@ private readonly
 /// ExtendedPathFilter filters based on name, file size, and the last write time of the file.
 /// 
 /// Provides an example of how to customise filtering.
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class ExtendedPathFilter : PathFilter
 {
     #region Constructors
@@ -240,7 +242,7 @@ public DateTime MaxDate
 /// NameAndSizeFilter filters based on name and file size.
 /// 
 /// A sample showing how filters might be extended.
-[Obsolete("Use ExtendedPathFilter instead")]
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class NameAndSizeFilter : PathFilter
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/PathUtils.cs
similarity index 95%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/PathUtils.cs
index 712de7bc5..62801fe85 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/PathUtils.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/PathUtils.cs
@@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 /// 
 /// PathUtils provides simple utilities for handling paths.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public static class PathUtils
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
index 9d1a8c6c1..ff300a153 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Core/StreamUtils.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Core;
 /// 
 /// Provides simple " utilities.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public sealed class StreamUtils
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
similarity index 96%
rename from MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
index 01911d956..667c6a141 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/PkzipClassic.cs
@@ -9,6 +9,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption;
 /// While it has been superceded by more recent and more powerful algorithms, its still in use and
 /// is viable for preventing casual snooping
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public abstract class PkzipClassic : SymmetricAlgorithm
 {
     /// 
@@ -63,6 +64,7 @@ public static byte[] GenerateKeys(byte[] seed)
 /// PkzipClassicCryptoBase provides the low level facilities for encryption
 /// and decryption using the PkzipClassic algorithm.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal class PkzipClassicCryptoBase
 {
     /// 
@@ -130,6 +132,7 @@ protected void Reset()
 /// 
 /// PkzipClassic CryptoTransform for encryption.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal class PkzipClassicEncryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform
 {
     /// 
@@ -241,6 +244,7 @@ public void Dispose()
 /// 
 /// PkzipClassic CryptoTransform for decryption.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal class PkzipClassicDecryptCryptoTransform : PkzipClassicCryptoBase, ICryptoTransform
 {
     /// 
@@ -353,6 +357,7 @@ public void Dispose()
 /// Defines a wrapper object to access the Pkzip algorithm.
 /// This class cannot be inherited.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public sealed class PkzipClassicManaged : PkzipClassic
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
index 417d1ba8b..b8760bb27 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/ZipAESStream.cs
@@ -15,6 +15,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption;
 /// Based on information from http://www.winzip.com/aes_info.htm
 /// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal class ZipAESStream : CryptoStream
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
index 0f3f34bef..6db98930b 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Encryption/ZipAESTransform.cs
@@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Encryption;
 /// 
 /// Transforms stream using AES in CTR mode
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 internal class ZipAESTransform : ICryptoTransform
 {
     private class IncrementalHash : HMACSHA1
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs
similarity index 89%
rename from MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs
index 00160215e..fd7dbc3e7 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZIPConstants.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.GZip;
 /// 
 /// This class contains constants used for gzip.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")]
 public sealed class GZipConstants
 {
@@ -46,6 +47,7 @@ public static Encoding Encoding
 /// 
 /// GZip header flags
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Flags]
 public enum GZipFlags : byte
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZip.cs
similarity index 93%
rename from MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZip.cs
index 9e561e493..068643a70 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZip.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZip.cs
@@ -1,12 +1,13 @@
-using System;
+using MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression;
+using System;
 using System.IO;
-using static MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Deflater;
 
 namespace MelonLoader.ICSharpCode.SharpZipLib.GZip;
 
 /// 
 /// An example class to demonstrate compression and decompression of GZip streams.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public static class GZip
 {
     /// 
@@ -64,7 +65,7 @@ public static void Compress(Stream inStream, Stream outStream, bool isStreamOwne
         if (bufferSize < 512)
             throw new ArgumentOutOfRangeException(nameof(bufferSize), "Deflate buffer size must be >= 512");
 
-        if (level is < NO_COMPRESSION or > BEST_COMPRESSION)
+        if (level is < Deflater.NO_COMPRESSION or > Deflater.BEST_COMPRESSION)
             throw new ArgumentOutOfRangeException(nameof(level), "Compression level must be 0-9");
 
         try
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZipException.cs
similarity index 94%
rename from MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZipException.cs
index 3d4130833..eb7176bdc 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GZipException.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GZipException.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.GZip;
 /// 
 /// GZipException represents exceptions specific to GZip classes and code.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class GZipException : SharpZipBaseException
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
similarity index 99%
rename from MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
index 471a84c4b..53ae98f46 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GzipInputStream.cs
@@ -33,6 +33,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.GZip;
 /// }
 /// 
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class GZipInputStream : InflaterInputStream
 {
     #region Instance Fields
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs
similarity index 98%
rename from MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs
index d06cf3465..9dda84f43 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/GZip/GzipOutputStream.cs
@@ -34,6 +34,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.GZip;
 /// }
 /// 
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class GZipOutputStream : DeflaterOutputStream
 {
     private enum OutputState
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/LICENSE.txt b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/LICENSE.txt
similarity index 100%
rename from MelonLoader/ICSharpCode/SharpZipLib/LICENSE.txt
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/LICENSE.txt
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs
similarity index 92%
rename from MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs
index 81c740b9d..a3b05d907 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwConstants.cs
@@ -1,8 +1,11 @@
+using System;
+
 namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw;
 
 /// 
 /// This class contains constants used for LZW
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")]
 public sealed class LzwConstants
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwException.cs
similarity index 94%
rename from MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwException.cs
index 434c31be9..c5e982bab 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwException.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwException.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw;
 /// 
 /// LzwException represents exceptions specific to LZW classes and code.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class LzwException : SharpZipBaseException
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs
similarity index 99%
rename from MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs
index 2801ce7f5..da8091757 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Lzw/LzwInputStream.cs
@@ -41,6 +41,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Lzw;
 /// }
 /// 
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class LzwInputStream : Stream
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/README.md b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/README.md
similarity index 100%
rename from MelonLoader/ICSharpCode/SharpZipLib/README.md
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/README.md
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/SharpZipBaseException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
similarity index 95%
rename from MelonLoader/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
index 3f2c05275..5d6dd2999 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/SharpZipBaseException.cs
@@ -9,6 +9,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib;
 /// 
 /// NOTE: Not all exceptions thrown will be derived from this class.
 /// A variety of other exceptions are possible for example 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class SharpZipBaseException : Exception
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/StreamDecodingException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/StreamDecodingException.cs
similarity index 95%
rename from MelonLoader/ICSharpCode/SharpZipLib/StreamDecodingException.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/StreamDecodingException.cs
index 60cb3ede7..de6c1a249 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/StreamDecodingException.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/StreamDecodingException.cs
@@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib;
 /// Indicates that an error occurred during decoding of a input stream due to corrupt
 /// data or (unintentional) library incompatibility.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class StreamDecodingException : SharpZipBaseException
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
similarity index 95%
rename from MelonLoader/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
index 3c1214345..777a504c3 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/StreamUnsupportedException.cs
@@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib;
 /// 
 /// Indicates that the input stream could not decoded due to known library incompability or missing features
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class StreamUnsupportedException : StreamDecodingException
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs
similarity index 94%
rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs
index 21bf059a0..b84d04f55 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/InvalidHeaderException.cs
@@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar;
 /// This exception is used to indicate that there is a problem
 /// with a TAR archive header.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 [Serializable]
 public class InvalidHeaderException : TarException
 {
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
similarity index 99%
rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
index 400055d31..8559c9b99 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarArchive.cs
@@ -8,6 +8,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar;
 /// 
 /// Used to advise clients of 'events' while processing archives
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message);
 
 /// 
@@ -31,6 +32,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar;
 /// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock
 /// properties, this would be rather trivial.
 /// 
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)]
 public class TarArchive : IDisposable
 {
     /// 
diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
similarity index 99%
rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
index da2bfc8f5..f998823c7 100644
--- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
+++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarBuffer.cs
@@ -15,6 +15,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar;
 /// TarBuffers are created by Tar IO Streams.
 /// 

///
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class TarBuffer { /* A quote from GNU tar man file on blocking and records diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarEntry.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarEntry.cs index 48872cd3b..5656e2219 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarEntry.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarEntry.cs @@ -32,6 +32,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; /// defaults and the File is set to null.

/// ///
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class TarEntry { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarException.cs similarity index 94% rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarException.cs index cb6a9f57e..2c515ef2d 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarException.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarException.cs @@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; /// /// TarException represents exceptions specific to Tar classes and code. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] [Serializable] public class TarException : SharpZipBaseException { diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs similarity index 94% rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs index 23f7d2ab0..02ccc48be 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text; namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; @@ -6,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; /// /// Reads the extended header of a Tar stream /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class TarExtendedHeaderReader { private const byte LENGTH = 0; diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarHeader.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarHeader.cs index aba44ff6a..0d4921a29 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarHeader.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarHeader.cs @@ -38,6 +38,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; /// char t_mfill[12]; // 500 Filler up to 512 /// }; /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class TarHeader { #region Constants diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs index 3ac41aff5..c8c79c565 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs @@ -10,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; /// the archive, and the read each entry as a normal input stream /// using read(). ///
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class TarInputStream : Stream { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs index 6b2b277ea..562001a6a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarOutputStream.cs @@ -10,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Tar; /// by writing to this stream using write(). ///
/// public +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class TarOutputStream : Stream { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs similarity index 95% rename from MelonLoader/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs index fe7cd0d4b..4a7c6f78a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/UnexpectedEndOfStreamException.cs @@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib; /// /// Indicates that the input stream could not decoded due to the stream ending before enough data had been provided /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] [Serializable] public class UnexpectedEndOfStreamException : StreamDecodingException { diff --git a/MelonLoader/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs similarity index 96% rename from MelonLoader/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs index fcd7c9acd..20317e88a 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/ValueOutOfRangeException.cs @@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib; /// /// Indicates that a value was outside of the expected range when decoding an input stream /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] [Serializable] public class ValueOutOfRangeException : StreamDecodingException { diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs index d139efb74..96f91c743 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Deflater.cs @@ -12,6 +12,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// author of the original java version : Jochen Hoenicke ///
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class Deflater { #region Deflater Documentation diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs similarity index 97% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs index bb346ae91..384074ae5 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterConstants.cs @@ -5,6 +5,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// This class contains constants used for deflation. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] public static class DeflaterConstants { diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs index 2d9e44ddc..2c71e0de2 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterEngine.cs @@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// Strategies for deflater /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum DeflateStrategy { /// @@ -44,6 +45,7 @@ public enum DeflateStrategy /// Low level compression engine for deflate algorithm which uses a 32K sliding window /// with secondary compression from Huffman/Shannon-Fano codes. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class DeflaterEngine { #region Constants diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs index 6aad21927..30e83842b 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterHuffman.cs @@ -10,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// author of the original java version : Jochen Hoenicke ///
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class DeflaterHuffman { private const int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs similarity index 78% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs index 6e6996cd2..063806370 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/DeflaterPending.cs @@ -1,3 +1,5 @@ +using System; + namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// @@ -5,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// author of the original java version : Jochen Hoenicke /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class DeflaterPending : PendingBuffer { /// diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs index 404a2e7ea..aeccb4c2e 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Inflater.cs @@ -29,6 +29,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// author of the original java version : John Leuner, Jochen Hoenicke /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class Inflater { #region Constants/Readonly diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs index 63b1c557f..447ea7421 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/InflaterDynHeader.cs @@ -1,8 +1,10 @@ using MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; +using System; using System.Collections.Generic; namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] internal class InflaterDynHeader { #region Constants diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs index 8ab423698..2e115132c 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs @@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// Huffman tree used for inflation /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class InflaterHuffmanTree { #region Constants diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs index 0a765ce9c..aed858d90 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/PendingBuffer.cs @@ -1,3 +1,5 @@ +using System; + namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// @@ -8,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; /// /// author of the original java version : Jochen Hoenicke /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class PendingBuffer { #region Instance Fields diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs index 0327d3b13..04824ccf8 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs @@ -10,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; /// written to it. It uses a Deflater to perform actual deflating.
/// Authors of the original java version : Tom Tromey, Jochen Hoenicke /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class DeflaterOutputStream : Stream { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs index 211dd9573..9b0aafd59 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs @@ -10,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; /// /// The buffer supports decryption of incoming data. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class InflaterInputBuffer { #region Constructors @@ -324,6 +325,7 @@ public ICryptoTransform CryptoTransform /// /// Author of the original java version : John Leuner. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class InflaterInputStream : Stream { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs index 25b0f6801..2ea62d0e3 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/OutputWindow.cs @@ -8,6 +8,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; /// to repeat stuff.
/// Author of the original java version : John Leuner /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class OutputWindow { #region Constants diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs index 1d484aad7..5e57693c6 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/Compression/Streams/StreamManipulator.cs @@ -17,6 +17,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Streams; /// /// authors of the original java version : John Leuner, Jochen Hoenicke /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class StreamManipulator { /// diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/FastZip.cs similarity index 94% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/FastZip.cs index da3a8f307..4f9ab58af 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/FastZip.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/FastZip.cs @@ -2,14 +2,13 @@ using MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression; using System; using System.IO; -using static MelonLoader.ICSharpCode.SharpZipLib.Zip.Compression.Deflater; -using static MelonLoader.ICSharpCode.SharpZipLib.Zip.ZipEntryFactory; namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// FastZipEvents supports all events applicable to FastZip operations. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class FastZipEvents { /// @@ -165,6 +164,7 @@ public TimeSpan ProgressInterval /// /// FastZip provides facilities for creating and extracting zip files. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class FastZip { #region Enumerations @@ -202,13 +202,13 @@ public FastZip() } /// - /// Initialise a new instance of using the specified + /// Initialise a new instance of using the specified /// - /// The time setting to use when creating or extracting Zip entries. - /// Using TimeSetting.LastAccessTime[Utc] when + /// The time setting to use when creating or extracting Zip entries. + /// Using TimeSetting.LastAccessTime[Utc] when /// creating an archive will set the file time to the moment of reading. /// - public FastZip(TimeSetting timeSetting) + public FastZip(ZipEntryFactory.TimeSetting timeSetting) { entryFactory_ = new ZipEntryFactory(timeSetting); restoreDateTimeOnExtract_ = true; @@ -741,31 +741,31 @@ private void ExtractFileEntry(ZipEntry entry, string targetName) { switch (entryFactory_.Setting) { - case TimeSetting.CreateTime: + case ZipEntryFactory.TimeSetting.CreateTime: File.SetCreationTime(targetName, entry.DateTime); break; - case TimeSetting.CreateTimeUtc: + case ZipEntryFactory.TimeSetting.CreateTimeUtc: File.SetCreationTimeUtc(targetName, entry.DateTime); break; - case TimeSetting.LastAccessTime: + case ZipEntryFactory.TimeSetting.LastAccessTime: File.SetLastAccessTime(targetName, entry.DateTime); break; - case TimeSetting.LastAccessTimeUtc: + case ZipEntryFactory.TimeSetting.LastAccessTimeUtc: File.SetLastAccessTimeUtc(targetName, entry.DateTime); break; - case TimeSetting.LastWriteTime: + case ZipEntryFactory.TimeSetting.LastWriteTime: File.SetLastWriteTime(targetName, entry.DateTime); break; - case TimeSetting.LastWriteTimeUtc: + case ZipEntryFactory.TimeSetting.LastWriteTimeUtc: File.SetLastWriteTimeUtc(targetName, entry.DateTime); break; - case TimeSetting.Fixed: + case ZipEntryFactory.TimeSetting.Fixed: File.SetLastWriteTime(targetName, entryFactory_.FixedDateTime); break; @@ -840,31 +840,31 @@ private void ExtractEntry(ZipEntry entry) { switch (entryFactory_.Setting) { - case TimeSetting.CreateTime: + case ZipEntryFactory.TimeSetting.CreateTime: Directory.SetCreationTime(dirName, entry.DateTime); break; - case TimeSetting.CreateTimeUtc: + case ZipEntryFactory.TimeSetting.CreateTimeUtc: Directory.SetCreationTimeUtc(dirName, entry.DateTime); break; - case TimeSetting.LastAccessTime: + case ZipEntryFactory.TimeSetting.LastAccessTime: Directory.SetLastAccessTime(dirName, entry.DateTime); break; - case TimeSetting.LastAccessTimeUtc: + case ZipEntryFactory.TimeSetting.LastAccessTimeUtc: Directory.SetLastAccessTimeUtc(dirName, entry.DateTime); break; - case TimeSetting.LastWriteTime: + case ZipEntryFactory.TimeSetting.LastWriteTime: Directory.SetLastWriteTime(dirName, entry.DateTime); break; - case TimeSetting.LastWriteTimeUtc: + case ZipEntryFactory.TimeSetting.LastWriteTimeUtc: Directory.SetLastWriteTimeUtc(dirName, entry.DateTime); break; - case TimeSetting.Fixed: + case ZipEntryFactory.TimeSetting.Fixed: Directory.SetLastWriteTime(dirName, entryFactory_.FixedDateTime); break; @@ -932,7 +932,7 @@ private static bool NameIsValid(string name) private IEntryFactory entryFactory_ = new ZipEntryFactory(); private INameTransform extractNameTransform_; private UseZip64 useZip64_ = UseZip64.Dynamic; - private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION; + private Deflater.CompressionLevel compressionLevel_ = Deflater.CompressionLevel.DEFAULT_COMPRESSION; private string password_; diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs similarity index 91% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs index 04ca30c9e..3b2b0212c 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/IEntryFactory.cs @@ -1,12 +1,12 @@ using MelonLoader.ICSharpCode.SharpZipLib.Core; using System; -using static MelonLoader.ICSharpCode.SharpZipLib.Zip.ZipEntryFactory; namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// Defines factory methods for creating new values. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public interface IEntryFactory { /// @@ -54,12 +54,12 @@ public interface IEntryFactory INameTransform NameTransform { get; set; } /// - /// Get the in use. + /// Get the in use. /// - TimeSetting Setting { get; } + ZipEntryFactory.TimeSetting Setting { get; } /// - /// Get the value to use when is set to , + /// Get the value to use when is set to , /// or if not specified, the value of when the class was the initialized /// DateTime FixedDateTime { get; } diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs index 9db1f4ba7..402a06867 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/WindowsNameTransform.cs @@ -8,6 +8,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// WindowsNameTransform transforms names to windows compatible ones. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class WindowsNameTransform : INameTransform { /// diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs similarity index 96% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs index c890958d2..a833d1665 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipConstants.cs @@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// Determines how entries are tested to see if they should use Zip64 extensions or not. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum UseZip64 { /// @@ -29,6 +30,7 @@ public enum UseZip64 /// /// The kind of compression used for an entry in an archive /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum CompressionMethod { /// @@ -71,6 +73,7 @@ public enum CompressionMethod /// /// Identifies the encryption algorithm used for an entry /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum EncryptionAlgorithm { /// @@ -147,6 +150,7 @@ public enum EncryptionAlgorithm /// /// Defines the contents of the general bit flags field for an archive entry. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] [Flags] public enum GeneralBitFlags { @@ -237,6 +241,7 @@ public enum GeneralBitFlags /// /// This class contains constants used for Zip format files /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] public static class ZipConstants { diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs similarity index 73% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs index 652e6916d..0f26e701b 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEncryptionMethod.cs @@ -1,8 +1,11 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; +using System; + +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// The method of encrypting entries when creating zip archives. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum ZipEncryptionMethod { /// diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs index 2c892f27e..8c7896fea 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntry.cs @@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// Defines known values for the property. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum HostSystemID { /// @@ -123,6 +124,7 @@ public enum HostSystemID ///
///
Author of the original java version : Jochen Hoenicke ///
+[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class ZipEntry { [Flags] diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs similarity index 85% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs index e3a61fd02..a5ec60e88 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntryExtensions.cs @@ -1,8 +1,11 @@ -namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; +using System; + +namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// General ZipEntry helper extensions /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public static class ZipEntryExtensions { /// diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs index a69b2f22f..1391ba51b 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipEntryFactory.cs @@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// Basic implementation of /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class ZipEntryFactory : IEntryFactory { #region Enumerations diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipException.cs similarity index 94% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipException.cs index 25c36df61..3bc81a3ef 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipException.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipException.cs @@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// ZipException represents exceptions specific to Zip classes and code. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] [Serializable] public class ZipException : SharpZipBaseException { diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs similarity index 97% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs index 2a42e16e4..0004d88a3 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipExtraData.cs @@ -10,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// ExtraData tagged value interface. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public interface ITaggedData { /// @@ -35,6 +36,7 @@ public interface ITaggedData /// /// A raw binary tagged value /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class RawTaggedData : ITaggedData { /// @@ -110,6 +112,7 @@ public byte[] Data /// /// Class representing extended unix date time values. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class ExtendedUnixData : ITaggedData { /// @@ -323,6 +326,7 @@ public Flags Include /// /// Class handling NT date time values. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class NTTaggedData : ITaggedData { /// @@ -479,6 +483,7 @@ public DateTime LastAccessTime /// /// A factory that creates tagged data instances. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] internal interface ITaggedDataFactory { /// @@ -503,6 +508,7 @@ internal interface ITaggedDataFactory /// means that for extra data created by passing in data can have the values modified by the caller /// in some circumstances. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public sealed class ZipExtraData : IDisposable { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipFile.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipFile.cs index c01e27778..2e653dc81 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipFile.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipFile.cs @@ -17,6 +17,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// Arguments used with KeysRequiredEvent /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class KeysRequiredEventArgs : EventArgs { #region Constructors @@ -79,6 +80,7 @@ public byte[] Key /// /// The strategy to apply to testing. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum TestStrategy { /// @@ -96,6 +98,7 @@ public enum TestStrategy /// The operation in progress reported by a during testing. /// /// TestArchive +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum TestOperation { /// @@ -133,6 +136,7 @@ public enum TestOperation /// Status returned by during testing. /// /// TestArchive +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class TestStatus { #region Constructors @@ -244,6 +248,7 @@ internal void SetBytesTested(long value) /// /// If the message is non-null an error has occured. If the message is null /// the operation as found in status has started. +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public delegate void ZipTestResultHandler(TestStatus status, string message); #endregion Test Definitions @@ -253,6 +258,7 @@ internal void SetBytesTested(long value) /// /// The possible ways of applying updates to an archive. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public enum FileUpdateMode { /// @@ -313,6 +319,7 @@ public enum FileUpdateMode /// } /// /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class ZipFile : IEnumerable, IDisposable { #region KeyHandling @@ -568,7 +575,6 @@ internal ZipFile() public void Close() { DisposeInternal(true); - GC.SuppressFinalize(this); } #endregion Destructors and Closing @@ -3299,6 +3305,8 @@ public Stream GetSource() void IDisposable.Dispose() { Close(); + + GC.SuppressFinalize(this); } #endregion IDisposable Members @@ -4353,6 +4361,7 @@ public override bool CanTimeout /// /// Provides a static way to obtain a source of data for an entry. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public interface IStaticDataSource { /// @@ -4367,6 +4376,7 @@ public interface IStaticDataSource /// Represents a source of data that can dynamically provide /// multiple data sources based on the parameters passed. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public interface IDynamicDataSource { /// @@ -4382,6 +4392,7 @@ public interface IDynamicDataSource /// /// Default implementation of a for use with files stored on disk. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class StaticDiskDataSource : IStaticDataSource { /// @@ -4418,6 +4429,7 @@ private readonly /// /// Default implementation of for files stored on disk. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class DynamicDiskDataSource : IDynamicDataSource { #region IDataSource Members @@ -4450,6 +4462,7 @@ public Stream GetSource(ZipEntry entry, string name) /// /// Defines facilities for data storage when updating Zip Archives. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public interface IArchiveStorage { /// @@ -4495,6 +4508,7 @@ public interface IArchiveStorage /// /// An abstract suitable for extension by inheritance. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public abstract class BaseArchiveStorage : IArchiveStorage { #region Constructors @@ -4570,6 +4584,7 @@ public FileUpdateMode UpdateMode /// /// An implementation suitable for hard disks. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class DiskArchiveStorage : BaseArchiveStorage { #region Constructors @@ -4722,6 +4737,7 @@ public override void Dispose() /// /// An implementation suitable for in memory streams. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class MemoryArchiveStorage : BaseArchiveStorage { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs index 2a433405b..8058525a6 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipHelperStream.cs @@ -6,6 +6,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// Holds data pertinent to a data descriptor. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class DescriptorData { /// @@ -44,6 +45,7 @@ public long Crc #endregion Instance Fields } +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] internal class EntryPatchData { public long SizePatchOffset @@ -69,6 +71,7 @@ public long CrcPatchOffset /// /// This class assists with writing/reading from Zip files. /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] internal class ZipHelperStream : Stream { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs index 633cbd0da..aa603dbb6 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipInputStream.cs @@ -55,6 +55,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// } /// /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class ZipInputStream : InflaterInputStream { #region Instance Fields diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs similarity index 97% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs index a7c059f2b..1dbab1174 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipNameTransform.cs @@ -10,6 +10,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// The use of absolute names is supported although its use is not valid /// according to Zip naming conventions, and should not be used if maximum compatability is desired. +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class ZipNameTransform : INameTransform { #region Constructors @@ -228,6 +229,7 @@ public static bool IsValidName(string name) /// An implementation of INameTransform that transforms entry paths as per the Zip file naming convention. /// Strips path roots and puts directory separators in the correct format ('/') /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class PathTransformer : INameTransform { /// diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs similarity index 99% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs index 523d27c16..d2bba2b8e 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipOutputStream.cs @@ -53,6 +53,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// } /// /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public class ZipOutputStream : DeflaterOutputStream { #region Constructors diff --git a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs similarity index 98% rename from MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs rename to MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs index 284f2ffae..ae4ef9608 100644 --- a/MelonLoader/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Zip/ZipStrings.cs @@ -7,6 +7,7 @@ namespace MelonLoader.ICSharpCode.SharpZipLib.Zip; /// /// This static class contains functions for encoding and decoding zip file strings /// +[Obsolete("Please use an alternative library instead. This will be removed in a future version.", true)] public static class ZipStrings { static ZipStrings() diff --git a/MelonLoader/ISupportModule_From.cs b/MelonLoader/BackwardsCompatibility/ISupportModule_From.cs similarity index 84% rename from MelonLoader/ISupportModule_From.cs rename to MelonLoader/BackwardsCompatibility/ISupportModule_From.cs index b6987f6d2..cafd03b37 100644 --- a/MelonLoader/ISupportModule_From.cs +++ b/MelonLoader/BackwardsCompatibility/ISupportModule_From.cs @@ -1,5 +1,8 @@ -namespace MelonLoader; +using System; +namespace MelonLoader; + +[Obsolete("Why is this public???", true)] public interface ISupportModule_From { void OnApplicationLateStart(); @@ -13,4 +16,4 @@ public interface ISupportModule_From void Quit(); void DefiniteQuit(); void SetInteropSupportInterface(InteropSupport.Interface interop); -} \ No newline at end of file +} diff --git a/MelonLoader/BackwardsCompatibility/ISupportModule_To.cs b/MelonLoader/BackwardsCompatibility/ISupportModule_To.cs new file mode 100644 index 000000000..bc58aaf4b --- /dev/null +++ b/MelonLoader/BackwardsCompatibility/ISupportModule_To.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections; + +namespace MelonLoader; + +[Obsolete("Why is this public???", true)] +public interface ISupportModule_To +{ + object StartCoroutine(IEnumerator coroutine); + void StopCoroutine(object coroutineToken); + void UnityDebugLog(string msg); +} diff --git a/MelonLoader/TinyJSON/Decoder.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/Decoder.cs similarity index 98% rename from MelonLoader/TinyJSON/Decoder.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/Decoder.cs index 9f563dcba..2552474cc 100644 --- a/MelonLoader/TinyJSON/Decoder.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/Decoder.cs @@ -5,6 +5,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class Decoder : IDisposable { private const string whiteSpace = " \t\n\r"; diff --git a/MelonLoader/TinyJSON/EncodeOptions.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/EncodeOptions.cs similarity index 73% rename from MelonLoader/TinyJSON/EncodeOptions.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/EncodeOptions.cs index 1061b3614..447f85e48 100644 --- a/MelonLoader/TinyJSON/EncodeOptions.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/EncodeOptions.cs @@ -2,6 +2,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] [Flags] public enum EncodeOptions { diff --git a/MelonLoader/TinyJSON/Encoder.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/Encoder.cs similarity index 99% rename from MelonLoader/TinyJSON/Encoder.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/Encoder.cs index 9193e84c9..7b86f4bf4 100644 --- a/MelonLoader/TinyJSON/Encoder.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/Encoder.cs @@ -7,6 +7,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class Encoder { private static readonly Type includeAttrType = typeof(Include); diff --git a/MelonLoader/TinyJSON/Extensions.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/Extensions.cs similarity index 84% rename from MelonLoader/TinyJSON/Extensions.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/Extensions.cs index dda6aae04..b0440f68a 100644 --- a/MelonLoader/TinyJSON/Extensions.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/Extensions.cs @@ -3,6 +3,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public static class Extensions { public static bool AnyOfType(this IEnumerable source, Type expectedType) diff --git a/MelonLoader/TinyJSON/JSON.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/JSON.cs similarity index 94% rename from MelonLoader/TinyJSON/JSON.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/JSON.cs index af702c7e2..a07572ace 100644 --- a/MelonLoader/TinyJSON/JSON.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/JSON.cs @@ -10,6 +10,7 @@ namespace MelonLoader.TinyJSON; /// Mark members that should be included. /// Public fields are included by default. /// +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class Include : Attribute { } @@ -17,24 +18,28 @@ public sealed class Include : Attribute { } /// Mark members that should be excluded. /// Private fields and all properties are excluded by default. /// +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class Exclude : Attribute { } /// /// Mark methods to be called after an object is decoded. /// +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class AfterDecode : Attribute { } /// /// Mark methods to be called before an object is encoded. /// +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class BeforeEncode : Attribute { } /// /// Mark members to force type hinting even when EncodeOptions.NoTypeHints is set. /// +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class TypeHint : Attribute { } @@ -42,6 +47,7 @@ public class TypeHint : Attribute { } /// Provide field and property aliases when an object is decoded. /// If a field or property is not found while decoding, this list will be searched for a matching alias. /// +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] public class DecodeAlias : Attribute { @@ -58,14 +64,15 @@ public bool Contains(string name) } } -[Obsolete("Use the Exclude attribute instead.")] +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] // ReSharper disable once UnusedMember.Global public sealed class Skip : Exclude { } -[Obsolete("Use the AfterDecode attribute instead.")] +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] // ReSharper disable once UnusedMember.Global public sealed class Load : AfterDecode { } +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class DecodeException : Exception { public DecodeException(string message) @@ -76,6 +83,7 @@ public DecodeException(string message, Exception innerException) } // ReSharper disable once InconsistentNaming +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public static class JSON { private static readonly Type includeAttrType = typeof(Include); diff --git a/MelonLoader/TinyJSON/LICENSE.md b/MelonLoader/BackwardsCompatibility/TinyJSON/LICENSE.md similarity index 100% rename from MelonLoader/TinyJSON/LICENSE.md rename to MelonLoader/BackwardsCompatibility/TinyJSON/LICENSE.md diff --git a/MelonLoader/TinyJSON/ProxyArray.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyArray.cs similarity index 92% rename from MelonLoader/TinyJSON/ProxyArray.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/ProxyArray.cs index 9f937c7f1..fed3c31ce 100644 --- a/MelonLoader/TinyJSON/ProxyArray.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyArray.cs @@ -1,8 +1,10 @@ +using System; using System.Collections; using System.Collections.Generic; namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class ProxyArray : Variant, IEnumerable { private readonly List list; diff --git a/MelonLoader/TinyJSON/ProxyBoolean.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyBoolean.cs similarity index 77% rename from MelonLoader/TinyJSON/ProxyBoolean.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/ProxyBoolean.cs index e1193022f..464e9414e 100644 --- a/MelonLoader/TinyJSON/ProxyBoolean.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyBoolean.cs @@ -2,6 +2,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class ProxyBoolean : Variant { private readonly bool value; diff --git a/MelonLoader/TinyJSON/ProxyNumber.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyNumber.cs similarity index 96% rename from MelonLoader/TinyJSON/ProxyNumber.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/ProxyNumber.cs index 5f21ce383..3d8e7821b 100644 --- a/MelonLoader/TinyJSON/ProxyNumber.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyNumber.cs @@ -3,6 +3,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class ProxyNumber : Variant { private static readonly char[] floatingPointCharacters = { '.', 'e' }; diff --git a/MelonLoader/TinyJSON/ProxyObject.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyObject.cs similarity index 92% rename from MelonLoader/TinyJSON/ProxyObject.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/ProxyObject.cs index 26b7e8fb0..1e7533b3d 100644 --- a/MelonLoader/TinyJSON/ProxyObject.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyObject.cs @@ -1,9 +1,11 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class ProxyObject : Variant, IEnumerable> { public const string TypeHintKey = "@type"; diff --git a/MelonLoader/TinyJSON/ProxyString.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyString.cs similarity index 72% rename from MelonLoader/TinyJSON/ProxyString.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/ProxyString.cs index b29da1241..874eb4eee 100644 --- a/MelonLoader/TinyJSON/ProxyString.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/ProxyString.cs @@ -2,6 +2,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public sealed class ProxyString : Variant { private readonly string value; diff --git a/MelonLoader/TinyJSON/README.md b/MelonLoader/BackwardsCompatibility/TinyJSON/README.md similarity index 100% rename from MelonLoader/TinyJSON/README.md rename to MelonLoader/BackwardsCompatibility/TinyJSON/README.md diff --git a/MelonLoader/TinyJSON/Variant.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/Variant.cs similarity index 97% rename from MelonLoader/TinyJSON/Variant.cs rename to MelonLoader/BackwardsCompatibility/TinyJSON/Variant.cs index cf7c9aa22..7c0d9b16c 100644 --- a/MelonLoader/TinyJSON/Variant.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/Variant.cs @@ -3,6 +3,7 @@ namespace MelonLoader.TinyJSON; +[Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public abstract class Variant : IConvertible { protected static readonly IFormatProvider FormatProvider = new NumberFormatInfo(); diff --git a/MelonLoader/MelonAssembly.cs b/MelonLoader/MelonAssembly.cs index e5fb3c14b..ff7c8af49 100644 --- a/MelonLoader/MelonAssembly.cs +++ b/MelonLoader/MelonAssembly.cs @@ -23,19 +23,19 @@ public sealed class MelonAssembly public static event LemonFunc CustomMelonResolvers; - internal static List loadedAssemblies = []; + internal static List _loadedAssemblies = []; /// /// List of all loaded MelonAssemblies. /// - public static ReadOnlyCollection LoadedAssemblies => loadedAssemblies.AsReadOnly(); + public static ReadOnlyCollection LoadedAssemblies => _loadedAssemblies.AsReadOnly(); /// /// Tries to find the instance of Melon with type T, whether it's registered or not /// public static T FindMelonInstance() where T : MelonBase { - foreach (var asm in loadedAssemblies) + foreach (var asm in _loadedAssemblies) { foreach (var melon in asm.loadedMelons) { @@ -59,7 +59,7 @@ public static MelonAssembly GetMelonAssemblyOfMember(MemberInfo member, object o return melon.MelonAssembly; var name = member.DeclaringType.Assembly.FullName; - var ma = loadedAssemblies.Find(x => x.Assembly.FullName == name); + var ma = _loadedAssemblies.Find(x => x.Assembly.FullName == name); return ma; } @@ -138,7 +138,7 @@ public static MelonAssembly LoadMelonAssembly(string path, Assembly assembly, bo return null; } - var ma = loadedAssemblies.Find(x => x.Assembly.FullName == assembly.FullName); + var ma = _loadedAssemblies.Find(x => x.Assembly.FullName == assembly.FullName); if (ma != null) return ma; @@ -148,7 +148,7 @@ public static MelonAssembly LoadMelonAssembly(string path, Assembly assembly, bo OnAssemblyResolving.Invoke(assembly); ma = new MelonAssembly(assembly, path); - loadedAssemblies.Add(ma); + _loadedAssemblies.Add(ma); if (loadMelons) ma.LoadMelons(); diff --git a/MelonLoader/MelonBase.cs b/MelonLoader/MelonBase.cs index 4ad4f5ff4..72a175363 100644 --- a/MelonLoader/MelonBase.cs +++ b/MelonLoader/MelonBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.IO; using System.Linq; @@ -13,6 +14,7 @@ namespace MelonLoader; +[SuppressMessage("Naming", "CA1708: Identifiers should differ by more than case", Justification = "The faulty member is already deprecated")] public abstract class MelonBase { #region Static diff --git a/MelonLoader/MelonLoader.csproj b/MelonLoader/MelonLoader.csproj index 9a5d107e2..4a7740a7d 100644 --- a/MelonLoader/MelonLoader.csproj +++ b/MelonLoader/MelonLoader.csproj @@ -27,6 +27,7 @@ + diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs index 9ef6f6772..372eea924 100644 --- a/MelonLoader/MelonUtils.cs +++ b/MelonLoader/MelonUtils.cs @@ -226,6 +226,7 @@ public static string ToString(this byte[] data, string format, IFormatProvider p return result.ToString(); } + [Obsolete("Please use Newtonsoft.Json or System.Text.Json instead. This will be removed in a future version.", true)] public static T ParseJSONStringtoStruct(string jsonstr) { if (string.IsNullOrEmpty(jsonstr)) diff --git a/MelonLoader/Modules/ISupportModuleFrom.cs b/MelonLoader/Modules/ISupportModuleFrom.cs new file mode 100644 index 000000000..68eca10b7 --- /dev/null +++ b/MelonLoader/Modules/ISupportModuleFrom.cs @@ -0,0 +1,16 @@ +namespace MelonLoader.Modules; + +internal interface ISupportModuleFrom +{ + void OnApplicationLateStart(); + void OnSceneWasLoaded(int buildIndex, string sceneName); + void OnSceneWasInitialized(int buildIndex, string sceneName); + void OnSceneWasUnloaded(int buildIndex, string sceneName); + void Update(); + void FixedUpdate(); + void LateUpdate(); + void OnGUI(); + void Quit(); + void DefiniteQuit(); + void SetInteropSupportInterface(InteropSupport.Interface interop); +} \ No newline at end of file diff --git a/MelonLoader/ISupportModule_To.cs b/MelonLoader/Modules/ISupportModuleTo.cs similarity index 71% rename from MelonLoader/ISupportModule_To.cs rename to MelonLoader/Modules/ISupportModuleTo.cs index 9dcc1f262..0217d4483 100644 --- a/MelonLoader/ISupportModule_To.cs +++ b/MelonLoader/Modules/ISupportModuleTo.cs @@ -1,8 +1,8 @@ using System.Collections; -namespace MelonLoader; +namespace MelonLoader.Modules; -public interface ISupportModule_To +internal interface ISupportModuleTo { object StartCoroutine(IEnumerator coroutine); void StopCoroutine(object coroutineToken); diff --git a/MelonLoader/SupportModule.cs b/MelonLoader/SupportModule.cs index d00d026ca..1aaacaf2f 100644 --- a/MelonLoader/SupportModule.cs +++ b/MelonLoader/SupportModule.cs @@ -1,4 +1,5 @@ -using MelonLoader.Utils; +using MelonLoader.Modules; +using MelonLoader.Utils; using System; using System.Collections.Generic; using System.IO; @@ -8,7 +9,7 @@ namespace MelonLoader; internal static class SupportModule { - internal static ISupportModule_To Interface = null; + internal static ISupportModuleTo Interface = null; private static string BaseDirectory = null; private static readonly List Modules = @@ -91,7 +92,7 @@ private static bool LoadInterface(string ModulePath) return false; } - Interface = (ISupportModule_To)method.Invoke(null, new object[] { new SupportModule_From() }); + Interface = (ISupportModuleTo)method.Invoke(null, new object[] { new SupportModule_From() }); if (Interface == null) { MelonLogger.Error("Failed to Initialize Interface!"); diff --git a/MelonLoader/SupportModule_From.cs b/MelonLoader/SupportModule_From.cs index 55b764f9b..d5196f4c8 100644 --- a/MelonLoader/SupportModule_From.cs +++ b/MelonLoader/SupportModule_From.cs @@ -1,6 +1,8 @@ -namespace MelonLoader; +using MelonLoader.Modules; -internal class SupportModule_From : ISupportModule_From +namespace MelonLoader; + +internal class SupportModule_From : ISupportModuleFrom { public void OnApplicationLateStart() => MelonEvents.OnApplicationLateStart.Invoke(); From c42e9f03f20245efd1a2678f0c655e7e6a1740be Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 22:37:03 +0100 Subject: [PATCH 14/18] Fix warnings --- Dependencies/SupportModules/Component.cs | 2 +- MelonLoader/Fixes/Il2CppInteropFixes.cs | 4 +- MelonLoader/MelonBase.cs | 2 +- MelonLoader/MelonHandler.cs | 6 +- MelonLoader/MelonLaunchOptions.cs | 3 + MelonLoader/MelonLogger.cs | 74 +++++++++---------- MelonLoader/MelonPlatformAttribute.cs | 26 +++++-- MelonLoader/MelonPreferences.cs | 2 +- MelonLoader/MelonPreferences_Category.cs | 2 + MelonLoader/MelonPreferences_Entry.cs | 3 + MelonLoader/MelonProcessAttribute.cs | 18 +++-- MelonLoader/MelonUtils.cs | 2 +- .../MelonPreferences_ReflectiveCategory.cs | 2 + MelonLoader/Semver/IntExtensions.cs | 10 +-- MelonLoader/VerifyLoaderVersionAttribute.cs | 10 +-- 15 files changed, 94 insertions(+), 72 deletions(-) diff --git a/Dependencies/SupportModules/Component.cs b/Dependencies/SupportModules/Component.cs index 06e73d820..86c3e2d96 100644 --- a/Dependencies/SupportModules/Component.cs +++ b/Dependencies/SupportModules/Component.cs @@ -1,7 +1,7 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using UnityEngine; -using System.Diagnostics.CodeAnalysis; #if SM_Il2Cpp using Il2CppInterop.Runtime; diff --git a/MelonLoader/Fixes/Il2CppInteropFixes.cs b/MelonLoader/Fixes/Il2CppInteropFixes.cs index 12907b14f..86bf82302 100644 --- a/MelonLoader/Fixes/Il2CppInteropFixes.cs +++ b/MelonLoader/Fixes/Il2CppInteropFixes.cs @@ -386,7 +386,7 @@ private static bool RewriteGlobalContext_GetNewAssemblyForOriginal_Prefix(Rewrit return false; } - assemblyName = assemblyName.StartsWith("Il2Cpp") ? assemblyName.Remove(0, 6) : $"Il2Cpp{assemblyName}"; + assemblyName = assemblyName.StartsWith("Il2Cpp") ? assemblyName[6..] : $"Il2Cpp{assemblyName}"; if (contexts.TryGetValue(assemblyName, out __result)) { @@ -421,7 +421,7 @@ private static bool RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix(Rewrite return false; } - assemblyName = assemblyName.StartsWith("Il2Cpp") ? assemblyName.Remove(0, 6) : $"Il2Cpp{assemblyName}"; + assemblyName = assemblyName.StartsWith("Il2Cpp") ? assemblyName[6..] : $"Il2Cpp{assemblyName}"; if (contexts.TryGetValue(assemblyName, out rewriteContext)) { //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}"); diff --git a/MelonLoader/MelonBase.cs b/MelonLoader/MelonBase.cs index 72a175363..b6b7ca11e 100644 --- a/MelonLoader/MelonBase.cs +++ b/MelonLoader/MelonBase.cs @@ -346,7 +346,7 @@ public static void PrintIncompatibilities(Incompatibility[] incompatibilities, M MelonLogger.MsgDirect($"- {melon.Info.Name} is only compatible with the following Process Names:"); foreach (var p in melon.SupportedProcesses) - MelonLogger.MsgDirect($" - '{p.EXE_Name}'"); + MelonLogger.MsgDirect($" - '{p.ExecutableName}'"); } if (incompatibilities.Contains(Incompatibility.Platform)) diff --git a/MelonLoader/MelonHandler.cs b/MelonLoader/MelonHandler.cs index c7a96bc23..0aa72e8ab 100644 --- a/MelonLoader/MelonHandler.cs +++ b/MelonLoader/MelonHandler.cs @@ -54,16 +54,16 @@ public static void LoadMelonsFromDirectory(string path) public static List Mods => MelonTypeBase.RegisteredMelons.ToList(); [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromFile(string filelocation, bool is_plugin) => LoadFromFile(filelocation); + public static void LoadFromFile(string filelocation, bool isPlugin) => LoadFromFile(filelocation); [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] public static void LoadFromByteArray(byte[] filedata, string filelocation) => LoadFromByteArray(filedata, filepath: filelocation); [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromByteArray(byte[] filedata, string filelocation, bool is_plugin) => LoadFromByteArray(filedata, filepath: filelocation); + public static void LoadFromByteArray(byte[] filedata, string filelocation, bool isPlugin) => LoadFromByteArray(filedata, filepath: filelocation); [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] - public static void LoadFromAssembly(Assembly asm, string filelocation, bool is_plugin) => LoadFromAssembly(asm, filelocation); + public static void LoadFromAssembly(Assembly asm, string filelocation, bool isPlugin) => LoadFromAssembly(asm, filelocation); [Obsolete("Use 'MelonBase.Hash' instead.")] public static string GetMelonHash(MelonBase melonBase) diff --git a/MelonLoader/MelonLaunchOptions.cs b/MelonLoader/MelonLaunchOptions.cs index 7f333e09c..f8a50c146 100644 --- a/MelonLoader/MelonLaunchOptions.cs +++ b/MelonLoader/MelonLaunchOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace MelonLoader; @@ -97,6 +98,7 @@ internal static void Load() #region Obsolete [Obsolete("Use LoaderConfig.Current.Loader instead.")] + [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's deprecated")] public static class Core { [Obsolete("This option isn't used anymore.")] @@ -181,6 +183,7 @@ public static class Il2CppAssemblyGenerator public static bool OfflineMode => LoaderConfig.Current.UnityEngine.ForceOfflineGeneration; [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion instead.")] + [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's deprecated")] public static string ForceVersion_Dumper => LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceGeneratorRegex instead.")] diff --git a/MelonLoader/MelonLogger.cs b/MelonLoader/MelonLogger.cs index ac81854dc..8478b677f 100644 --- a/MelonLoader/MelonLogger.cs +++ b/MelonLoader/MelonLogger.cs @@ -22,20 +22,20 @@ public class MelonLogger public static void Msg(string txt, params object[] args) => NativeMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); - public static void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); + public static void Msg(ConsoleColor textColor, object obj) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(textColor), null, obj.ToString()); - public static void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); + public static void Msg(ConsoleColor textColor, string txt) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(textColor), null, txt); - public static void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); + public static void Msg(ConsoleColor textColor, string txt, params object[] args) => NativeMsg(DefaultMelonColor, ConsoleColorToDrawingColor(textColor), null, string.Format(txt, args)); //Identical to Msg(Color, string) except it skips walking the stack to find a melon - public static void MsgDirect(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt, true); + public static void MsgDirect(Color textColor, string txt) => NativeMsg(DefaultMelonColor, textColor, null, txt, true); - public static void Msg(Color txt_color, object obj) => NativeMsg(DefaultMelonColor, txt_color, null, obj.ToString()); + public static void Msg(Color textColor, object obj) => NativeMsg(DefaultMelonColor, textColor, null, obj.ToString()); - public static void Msg(Color txt_color, string txt) => NativeMsg(DefaultMelonColor, txt_color, null, txt); + public static void Msg(Color textColor, string txt) => NativeMsg(DefaultMelonColor, textColor, null, txt); - public static void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); + public static void Msg(Color textColor, string txt, params object[] args) => NativeMsg(DefaultMelonColor, textColor, null, string.Format(txt, args)); //Identical to MsgPastel(string) except it skips walking the stack to find a melon internal static void MsgPastelDirect(string txt) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, txt, true); @@ -46,20 +46,20 @@ public class MelonLogger public static void MsgPastel(string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, DefaultTextColor, null, string.Format(txt, args)); - public static void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, obj.ToString()); + public static void MsgPastel(ConsoleColor textColor, object obj) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(textColor), null, obj.ToString()); - public static void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, txt); + public static void MsgPastel(ConsoleColor textColor, string txt) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(textColor), null, txt); - public static void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(txt_color), null, string.Format(txt, args)); + public static void MsgPastel(ConsoleColor textColor, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, ConsoleColorToDrawingColor(textColor), null, string.Format(txt, args)); //Identical to MsgPastel(Color, string) except it skips walking the stack to find a melon - public static void MsgPastelDirect(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt, true); + public static void MsgPastelDirect(Color textColor, string txt) => NativePastelMsg(DefaultMelonColor, textColor, null, txt, true); - public static void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DefaultMelonColor, txt_color, null, obj.ToString()); + public static void MsgPastel(Color textColor, object obj) => NativePastelMsg(DefaultMelonColor, textColor, null, obj.ToString()); - public static void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DefaultMelonColor, txt_color, null, txt); + public static void MsgPastel(Color textColor, string txt) => NativePastelMsg(DefaultMelonColor, textColor, null, txt); - public static void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, txt_color, null, string.Format(txt, args)); + public static void MsgPastel(Color textColor, string txt, params object[] args) => NativePastelMsg(DefaultMelonColor, textColor, null, string.Format(txt, args)); public static void Warning(object obj) => NativeWarning(null, obj.ToString()); @@ -79,7 +79,7 @@ public class MelonLogger public static void WriteLine(Color color, int length = 30) => MsgDirect(color, new string('-', length)); - private static void NativeMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) + private static void NativeMsg(Color namesection_color, Color textColor, string namesection, string txt, bool skipStackWalk = false) { if (namesection == null) { @@ -91,11 +91,11 @@ private static void NativeMsg(Color namesection_color, Color txt_color, string n } } - PassLogMsg(txt_color, txt ?? "null", namesection_color, namesection); - RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); + PassLogMsg(textColor, txt ?? "null", namesection_color, namesection); + RunMsgCallbacks(namesection_color, textColor, namesection, txt ?? "null"); } - private static void NativePastelMsg(Color namesection_color, Color txt_color, string namesection, string txt, bool skipStackWalk = false) + private static void NativePastelMsg(Color namesection_color, Color textColor, string namesection, string txt, bool skipStackWalk = false) { if (namesection == null) { @@ -107,8 +107,8 @@ private static void NativePastelMsg(Color namesection_color, Color txt_color, st } } - PastelMsg(namesection_color, txt_color, namesection, txt ?? "null"); - RunMsgCallbacks(namesection_color, txt_color, namesection, txt ?? "null"); + PastelMsg(namesection_color, textColor, namesection, txt ?? "null"); + RunMsgCallbacks(namesection_color, textColor, namesection, txt ?? "null"); } private static void NativeWarning(string namesection, string txt) @@ -138,10 +138,10 @@ public static void BigError(string namesection, string txt) PassLogError(new string('=', 50), namesection, false); } - internal static void RunMsgCallbacks(Color namesection_color, Color txt_color, string namesection, string txt) + internal static void RunMsgCallbacks(Color namesection_color, Color textColor, string namesection, string txt) { - MsgCallbackHandler?.Invoke(DrawingColorToConsoleColor(namesection_color), DrawingColorToConsoleColor(txt_color), namesection, txt); - MsgDrawingCallbackHandler?.Invoke(namesection_color, txt_color, namesection, txt); + MsgCallbackHandler?.Invoke(DrawingColorToConsoleColor(namesection_color), DrawingColorToConsoleColor(textColor), namesection, txt); + MsgDrawingCallbackHandler?.Invoke(namesection_color, textColor, namesection, txt); } [Obsolete("MsgCallbackHandler is obsolete. Please use MsgDrawingCallbackHandler for full Color support.")] @@ -183,17 +183,17 @@ private ConsoleColor Color public void Msg(string txt, params object[] args) => NativeMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); - public void Msg(ConsoleColor txt_color, object obj) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); + public void Msg(ConsoleColor textColor, object obj) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(textColor), Name, obj.ToString()); - public void Msg(ConsoleColor txt_color, string txt) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); + public void Msg(ConsoleColor textColor, string txt) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(textColor), Name, txt); - public void Msg(ConsoleColor txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); + public void Msg(ConsoleColor textColor, string txt, params object[] args) => NativeMsg(DrawingColor, ConsoleColorToDrawingColor(textColor), Name, string.Format(txt, args)); - public void Msg(Color txt_color, object obj) => NativeMsg(DrawingColor, txt_color, Name, obj.ToString()); + public void Msg(Color textColor, object obj) => NativeMsg(DrawingColor, textColor, Name, obj.ToString()); - public void Msg(Color txt_color, string txt) => NativeMsg(DrawingColor, txt_color, Name, txt); + public void Msg(Color textColor, string txt) => NativeMsg(DrawingColor, textColor, Name, txt); - public void Msg(Color txt_color, string txt, params object[] args) => NativeMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); + public void Msg(Color textColor, string txt, params object[] args) => NativeMsg(DrawingColor, textColor, Name, string.Format(txt, args)); public void MsgPastel(object obj) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, obj.ToString()); @@ -201,17 +201,17 @@ private ConsoleColor Color public void MsgPastel(string txt, params object[] args) => NativePastelMsg(DrawingColor, DefaultTextColor, Name, string.Format(txt, args)); - public void MsgPastel(ConsoleColor txt_color, object obj) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, obj.ToString()); + public void MsgPastel(ConsoleColor textColor, object obj) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(textColor), Name, obj.ToString()); - public void MsgPastel(ConsoleColor txt_color, string txt) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, txt); + public void MsgPastel(ConsoleColor textColor, string txt) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(textColor), Name, txt); - public void MsgPastel(ConsoleColor txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(txt_color), Name, string.Format(txt, args)); + public void MsgPastel(ConsoleColor textColor, string txt, params object[] args) => NativePastelMsg(DrawingColor, ConsoleColorToDrawingColor(textColor), Name, string.Format(txt, args)); - public void MsgPastel(Color txt_color, object obj) => NativePastelMsg(DrawingColor, txt_color, Name, obj.ToString()); + public void MsgPastel(Color textColor, object obj) => NativePastelMsg(DrawingColor, textColor, Name, obj.ToString()); - public void MsgPastel(Color txt_color, string txt) => NativePastelMsg(DrawingColor, txt_color, Name, txt); + public void MsgPastel(Color textColor, string txt) => NativePastelMsg(DrawingColor, textColor, Name, txt); - public void MsgPastel(Color txt_color, string txt, params object[] args) => NativePastelMsg(DrawingColor, txt_color, Name, string.Format(txt, args)); + public void MsgPastel(Color textColor, string txt, params object[] args) => NativePastelMsg(DrawingColor, textColor, Name, string.Format(txt, args)); public void Warning(object obj) => NativeWarning(Name, obj.ToString()); @@ -236,12 +236,12 @@ private ConsoleColor Color public void BigError(string txt) => MelonLogger.BigError(Name, txt); } - internal static void PastelMsg(Color namesection_color, Color txt_color, string namesection, string txt) + internal static void PastelMsg(Color namesection_color, Color textColor, string namesection, string txt) { // Regex to check for ANSI var cleanTxt = Regex.Replace(txt, @"(\x1B|\e|\033)\[(.*?)m", ""); - PassLogMsg(txt_color, cleanTxt, namesection_color, namesection); + PassLogMsg(textColor, cleanTxt, namesection_color, namesection); } internal static void Warning(string namesection, string txt) diff --git a/MelonLoader/MelonPlatformAttribute.cs b/MelonLoader/MelonPlatformAttribute.cs index 629be9bd6..c8e0a52a9 100644 --- a/MelonLoader/MelonPlatformAttribute.cs +++ b/MelonLoader/MelonPlatformAttribute.cs @@ -1,23 +1,35 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class MelonPlatformAttribute : Attribute +public class MelonPlatformAttribute(params MelonPlatformAttribute.CompatiblePlatforms[] platforms) : Attribute { - public MelonPlatformAttribute(params CompatiblePlatforms[] platforms) => Platforms = platforms; - // Enum for Melon Platform Compatibility. + [SuppressMessage("Naming", "CA1708: Identifiers should differ by more than case", Justification = "They're deprecated")] + [SuppressMessage("Naming", "CA1069: Enums should not have duplicate values", Justification = "They're deprecated")] public enum CompatiblePlatforms { - UNIVERSAL, - WINDOWS_X86, - WINDOWS_X64 + Universal = 0, + WindowsX86 = 1, + WindowsX64 = 2, + + [Obsolete("Use Universal (lower-case) instead.", true)] + UNIVERSAL = 0, + + [Obsolete("Use WindowsX86 (lower-case) instead.", true)] + [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's deprecated")] + WINDOWS_X86 = 1, + + [Obsolete("Use WindowsX64 (lower-case) instead.", true)] + [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's deprecated")] + WINDOWS_X64 = 2 }; // Platforms Compatible with the Melon. - public CompatiblePlatforms[] Platforms { get; internal set; } + public CompatiblePlatforms[] Platforms { get; internal set; } = platforms; public bool IsCompatible(CompatiblePlatforms platform) => Platforms == null || Platforms.Length == 0 || Platforms.Contains(platform); diff --git a/MelonLoader/MelonPreferences.cs b/MelonLoader/MelonPreferences.cs index 8d3be4543..dcf666581 100644 --- a/MelonLoader/MelonPreferences.cs +++ b/MelonLoader/MelonPreferences.cs @@ -183,7 +183,7 @@ public static MelonPreferences_Category CreateCategory(string identifier, string public static MelonPreferences_ReflectiveCategory CreateCategory(string identifier, string displayName = null) where T : new() => MelonPreferences_ReflectiveCategory.Create(identifier, displayName); - [Obsolete] + [Obsolete("Use the generic variant instead.", true)] public static MelonPreferences_Entry CreateEntry(string categoryIdentifier, string entryIdentifier, T defaultValue, string displayName, bool isHidden) => CreateEntry(categoryIdentifier, entryIdentifier, defaultValue, displayName, null, isHidden, false, null); diff --git a/MelonLoader/MelonPreferences_Category.cs b/MelonLoader/MelonPreferences_Category.cs index 16e31c440..6b4bfa0d3 100644 --- a/MelonLoader/MelonPreferences_Category.cs +++ b/MelonLoader/MelonPreferences_Category.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace MelonLoader; +[SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's public API")] public class MelonPreferences_Category { public readonly List Entries = []; diff --git a/MelonLoader/MelonPreferences_Entry.cs b/MelonLoader/MelonPreferences_Entry.cs index 1eb2f1db2..fd4209553 100644 --- a/MelonLoader/MelonPreferences_Entry.cs +++ b/MelonLoader/MelonPreferences_Entry.cs @@ -1,10 +1,12 @@ using System; +using System.Diagnostics.CodeAnalysis; using Tomlet; using Tomlet.Exceptions; using Tomlet.Models; namespace MelonLoader; +[SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's public API")] public abstract class MelonPreferences_Entry { public string Identifier { get; internal set; } @@ -45,6 +47,7 @@ protected void FireUntypedValueChanged(object old, object neew) public event Action OnValueChangedUntyped; } +[SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's public API")] public class MelonPreferences_Entry : MelonPreferences_Entry { private T myValue; diff --git a/MelonLoader/MelonProcessAttribute.cs b/MelonLoader/MelonProcessAttribute.cs index 36ac3ab35..60baa0d61 100644 --- a/MelonLoader/MelonProcessAttribute.cs +++ b/MelonLoader/MelonProcessAttribute.cs @@ -1,29 +1,37 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public class MelonProcessAttribute : Attribute { - public MelonProcessAttribute(string exe_name = null) - => EXE_Name = RemoveExtension(exe_name); + public MelonProcessAttribute(string exeName = null) + => ExecutableName = RemoveExtension(exeName); /// /// Name of the Game's Executable without the '.exe' extension. /// - public string EXE_Name { get; internal set; } + public string ExecutableName { get; internal set; } + + /// + /// Name of the Game's Executable without the '.exe' extension. + /// + [Obsolete("Use ExecutableName instead.", true)] + [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's deprecated")] + public string EXE_Name => ExecutableName; /// /// If the Attribute is set as Universal or not. /// public bool Universal - => string.IsNullOrEmpty(EXE_Name); + => string.IsNullOrEmpty(ExecutableName); /// /// Checks if the Attribute is compatible with or not. /// public bool IsCompatible(string processName) - => Universal || string.IsNullOrEmpty(processName) || (RemoveExtension(processName) == EXE_Name); + => Universal || string.IsNullOrEmpty(processName) || (RemoveExtension(processName) == ExecutableName); private string RemoveExtension(string name) => name == null ? null : (name.EndsWith(".exe") ? name[..^4] : name); diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs index 372eea924..1c76b378d 100644 --- a/MelonLoader/MelonUtils.cs +++ b/MelonLoader/MelonUtils.cs @@ -51,7 +51,7 @@ internal static void Setup(AppDomain domain) UnityInformationHandler.Setup(); CurrentGameAttribute = new MelonGameAttribute(UnityInformationHandler.GameDeveloper, UnityInformationHandler.GameName); - CurrentPlatform = IsGame32Bit() ? MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X86 : MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64; + CurrentPlatform = IsGame32Bit() ? MelonPlatformAttribute.CompatiblePlatforms.WindowsX86 : MelonPlatformAttribute.CompatiblePlatforms.WindowsX64; CurrentDomain = IsGameIl2Cpp() ? MelonPlatformDomainAttribute.CompatibleDomains.IL2CPP : MelonPlatformDomainAttribute.CompatibleDomains.MONO; } diff --git a/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs b/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs index 10fee5704..067dd3b8e 100644 --- a/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs +++ b/MelonLoader/Preferences/MelonPreferences_ReflectiveCategory.cs @@ -1,10 +1,12 @@ using System; +using System.Diagnostics.CodeAnalysis; using Tomlet; using Tomlet.Exceptions; using Tomlet.Models; namespace MelonLoader.Preferences; +[SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's public API")] public class MelonPreferences_ReflectiveCategory { private readonly Type SystemType; diff --git a/MelonLoader/Semver/IntExtensions.cs b/MelonLoader/Semver/IntExtensions.cs index 86e22be7b..4c79bbae3 100644 --- a/MelonLoader/Semver/IntExtensions.cs +++ b/MelonLoader/Semver/IntExtensions.cs @@ -19,13 +19,5 @@ internal static class IntExtensions /// fastest way to get a number of digits. /// public static int Digits(this int n) - { - if (n < 10) - return 1; - if (n < 100) - return 2; - if (n < 1_000) - return 3; - return n < 10_000 ? 4 : n < 100_000 ? 5 : n < 1_000_000 ? 6 : n < 10_000_000 ? 7 : n < 100_000_000 ? 8 : n < 1_000_000_000 ? 9 : 10; - } + => n < 10 ? 1 : n < 100 ? 2 : n < 1_000 ? 3 : n < 10_000 ? 4 : n < 100_000 ? 5 : n < 1_000_000 ? 6 : n < 10_000_000 ? 7 : n < 100_000_000 ? 8 : n < 1_000_000_000 ? 9 : 10; } diff --git a/MelonLoader/VerifyLoaderVersionAttribute.cs b/MelonLoader/VerifyLoaderVersionAttribute.cs index 80679d5cf..545d655ba 100644 --- a/MelonLoader/VerifyLoaderVersionAttribute.cs +++ b/MelonLoader/VerifyLoaderVersionAttribute.cs @@ -4,7 +4,7 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class VerifyLoaderVersionAttribute(SemVersion semver, bool is_minimum) : Attribute +public class VerifyLoaderVersionAttribute(SemVersion semver, bool isMinimum) : Attribute { /// /// Specified SemVersion. @@ -34,13 +34,13 @@ public class VerifyLoaderVersionAttribute(SemVersion semver, bool is_minimum) : /// /// If Version Specified is a Minimum. /// - public bool IsMinimum { get; private set; } = is_minimum; + public bool IsMinimum { get; private set; } = isMinimum; public VerifyLoaderVersionAttribute(int major, int minor, int patch) : this(new SemVersion(major, minor, patch), false) { } - public VerifyLoaderVersionAttribute(int major, int minor, int patch, string prerelease, bool is_minimum = false) : this(new SemVersion(major, minor, patch, prerelease), is_minimum) { } - public VerifyLoaderVersionAttribute(int major, int minor, int patch, bool is_minimum) : this(new SemVersion(major, minor, patch), is_minimum) { } + public VerifyLoaderVersionAttribute(int major, int minor, int patch, string prerelease, bool isMinimum = false) : this(new SemVersion(major, minor, patch, prerelease), isMinimum) { } + public VerifyLoaderVersionAttribute(int major, int minor, int patch, bool isMinimum) : this(new SemVersion(major, minor, patch), isMinimum) { } public VerifyLoaderVersionAttribute(string version) : this(version, false) { } - public VerifyLoaderVersionAttribute(string version, bool is_minimum) : this(SemVersion.Parse(version), is_minimum) { } + public VerifyLoaderVersionAttribute(string version, bool isMinimum) : this(SemVersion.Parse(version), isMinimum) { } public bool IsCompatible(SemVersion version) => SemVer == null || version == null || (IsMinimum ? SemVer <= version : SemVer == version); From ee8e2b33b93361c31485352875836bcdaf633d4c Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 22:43:13 +0100 Subject: [PATCH 15/18] Suppress Tomlet warnings --- MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj b/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj index c4178d124..4da0887d3 100644 --- a/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj +++ b/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj @@ -8,8 +8,9 @@ true $(MLOutDir) true - LNK4099 true + + $(NoWarn);LNK4099;IL3053 From 4bfb5bb6aae736fb44c7e6f4c236da330d04e4ed Mon Sep 17 00:00:00 2001 From: slxdy Date: Wed, 22 Jan 2025 23:45:21 +0100 Subject: [PATCH 16/18] Fix all remaining warnings and suggestions --- .../CompatibilityLayers/IPA/Module.cs | 7 +- .../Muse_Dash_Mono/Module.cs | 6 +- Dependencies/SupportModules/Il2Cpp/Main.cs | 34 ++--- MelonLoader.Bootstrap/Exports.cs | 1 + MelonLoader/Assertions/LemonAssert.cs | 16 +-- .../Assertions/LemonAssertException.cs | 9 +- .../BackwardsCompatibility/.editorconfig | 10 +- .../Harmony/Attributes.cs | 18 +-- .../Harmony/Extras/FastAccess.cs | 9 +- .../Harmony/HarmonyInstance.cs | 8 +- .../Harmony/HarmonyMethod.cs | 4 +- .../BackwardsCompatibility/Harmony/Patch.cs | 13 +- .../Tar/TarExtendedHeaderReader.cs | 2 +- .../SharpZipLib/Tar/TarInputStream.cs | 2 +- .../CoreClrUtils/CoreClrDelegateFixer.cs | 8 +- MelonLoader/CoreClrUtils/MethodBaseHelper.cs | 8 +- .../Fixes/DotnetAssemblyLoadContextFix.cs | 2 +- MelonLoader/Fixes/Il2CppICallInjector.cs | 8 +- MelonLoader/Fixes/Il2CppInteropFixes.cs | 11 +- MelonLoader/IniFile.cs | 28 +++- MelonLoader/InternalUtils/DependencyGraph.cs | 4 +- .../InternalUtils/UnityMagicMethods.cs | 130 +++++++++--------- MelonLoader/InteropSupport.cs | 4 +- MelonLoader/LemonArraySegment.cs | 2 +- MelonLoader/LemonEnumerator.cs | 4 +- MelonLoader/LoaderConfig.cs | 17 ++- .../MelonAdditionalCreditsAttribute.cs | 17 +-- .../MelonAdditionalDependenciesAttribute.cs | 6 +- MelonLoader/MelonEvent.cs | 56 +++----- MelonLoader/MelonGameAttribute.cs | 11 +- MelonLoader/MelonGameVersionAttribute.cs | 6 +- .../MelonIncompatibleAssembliesAttribute.cs | 6 +- MelonLoader/MelonMod.cs | 2 +- .../MelonOptionalDependenciesAttribute.cs | 6 +- MelonLoader/MelonPlatformDomainAttribute.cs | 6 +- MelonLoader/MelonPlugin.cs | 2 +- MelonLoader/MelonPriorityAttribute.cs | 6 +- MelonLoader/NativeUtils/CppUtils.cs | 2 +- MelonLoader/Pastel/Pastel.cs | 2 +- MelonLoader/Preferences/IO/Watcher.cs | 4 +- MelonLoader/ResolvedMelons.cs | 12 +- .../Resolver/SearchDirectoryManager.cs | 3 +- MelonLoader/RottenMelon.cs | 18 +-- MelonLoader/SupportModule.cs | 2 +- MelonLoader/VerifyLoaderBuildAttribute.cs | 6 +- .../Il2CppAssetBundle.cs | 62 +++++---- .../Il2CppAssetBundleManager.cs | 8 +- .../Il2CppAssetBundleRequest.cs | 29 ++-- 48 files changed, 295 insertions(+), 342 deletions(-) diff --git a/Dependencies/CompatibilityLayers/IPA/Module.cs b/Dependencies/CompatibilityLayers/IPA/Module.cs index cc4ac9f58..fec0eff43 100644 --- a/Dependencies/CompatibilityLayers/IPA/Module.cs +++ b/Dependencies/CompatibilityLayers/IPA/Module.cs @@ -21,10 +21,11 @@ public override void OnInitialize() // Point GetResolverFromAssembly to Dummy MelonCompatibilityLayer.Resolver string[] assembly_list = - { + [ "IllusionPlugin", "IllusionInjector", - }; + ]; + var base_assembly = typeof(IPA_Module).Assembly; foreach (var assemblyName in assembly_list) MelonAssemblyResolver.GetAssemblyResolveInfo(assemblyName).Override = base_assembly; @@ -53,7 +54,7 @@ private ResolvedMelons Resolve(Assembly asm) rotten.Add(rm); } - return new ResolvedMelons(melons.ToArray(), rotten.ToArray()); + return new ResolvedMelons([.. melons], [.. rotten]); } private MelonBase LoadPlugin(Assembly asm, Type pluginType, out RottenMelon rottenMelon) diff --git a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs index a31155428..4eccf7ae2 100644 --- a/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs +++ b/Dependencies/CompatibilityLayers/Muse_Dash_Mono/Module.cs @@ -20,10 +20,10 @@ public override void OnInitialize() // Inject Custom Resolver string[] assembly_list = - { + [ "ModHelper", "ModLoader", - }; + ]; var base_assembly = typeof(Muse_Dash_Mono_Module).Assembly; foreach (var assemblyName in assembly_list) MelonAssemblyResolver.GetAssemblyResolveInfo(assemblyName).Override = base_assembly; @@ -52,7 +52,7 @@ private ResolvedMelons Resolve(Assembly asm) rotten.Add(rm); } - return new ResolvedMelons(melons.ToArray(), rotten.ToArray()); + return new ResolvedMelons([.. melons], [.. rotten]); } private MelonBase LoadMod(Assembly asm, Type modType, out RottenMelon rottenMelon) diff --git a/Dependencies/SupportModules/Il2Cpp/Main.cs b/Dependencies/SupportModules/Il2Cpp/Main.cs index 5047eaf18..d94f710ac 100644 --- a/Dependencies/SupportModules/Il2Cpp/Main.cs +++ b/Dependencies/SupportModules/Il2Cpp/Main.cs @@ -84,39 +84,23 @@ private static void ConsoleCleaner() if (streamType == null) throw new Exception("Unable to Find Type Il2CppSystem.IO.Stream!"); - var propertyInfo = streamType.GetProperty("Null", BindingFlags.Static | BindingFlags.Public); - if (propertyInfo == null) - throw new Exception("Unable to Find Property Il2CppSystem.IO.Stream.Null!"); + var propertyInfo = streamType.GetProperty("Null", BindingFlags.Static | BindingFlags.Public) ?? throw new Exception("Unable to Find Property Il2CppSystem.IO.Stream.Null!"); - var nullStreamField = propertyInfo.GetGetMethod(); - if (nullStreamField == null) - throw new Exception("Unable to Find Get Method of Property Il2CppSystem.IO.Stream.Null!"); + var nullStreamField = propertyInfo.GetGetMethod() ?? throw new Exception("Unable to Find Get Method of Property Il2CppSystem.IO.Stream.Null!"); - var nullStream = nullStreamField.Invoke(null, new object[0]); - if (nullStream == null) - throw new Exception("Unable to Get Value of Property Il2CppSystem.IO.Stream.Null!"); + var nullStream = nullStreamField.Invoke(null, []) ?? throw new Exception("Unable to Get Value of Property Il2CppSystem.IO.Stream.Null!"); - var streamWriterType = Il2Cppmscorlib.GetType("Il2CppSystem.IO.StreamWriter"); - if (streamWriterType == null) - throw new Exception("Unable to Find Type Il2CppSystem.IO.StreamWriter!"); + var streamWriterType = Il2Cppmscorlib.GetType("Il2CppSystem.IO.StreamWriter") ?? throw new Exception("Unable to Find Type Il2CppSystem.IO.StreamWriter!"); - var streamWriterCtor = streamWriterType.GetConstructor(new[] { streamType }); - if (streamWriterCtor == null) - throw new Exception("Unable to Find Constructor of Type Il2CppSystem.IO.StreamWriter!"); + var streamWriterCtor = streamWriterType.GetConstructor([streamType]) ?? throw new Exception("Unable to Find Constructor of Type Il2CppSystem.IO.StreamWriter!"); - var nullStreamWriter = streamWriterCtor.Invoke(new[] { nullStream }); - if (nullStreamWriter == null) - throw new Exception("Unable to Invoke Constructor of Type Il2CppSystem.IO.StreamWriter!"); + var nullStreamWriter = streamWriterCtor.Invoke([nullStream]) ?? throw new Exception("Unable to Invoke Constructor of Type Il2CppSystem.IO.StreamWriter!"); - var consoleType = Il2Cppmscorlib.GetType("Il2CppSystem.Console"); - if (consoleType == null) - throw new Exception("Unable to Find Type Il2CppSystem.Console!"); + var consoleType = Il2Cppmscorlib.GetType("Il2CppSystem.Console") ?? throw new Exception("Unable to Find Type Il2CppSystem.Console!"); - var setOutMethod = consoleType.GetMethod("SetOut", BindingFlags.Static | BindingFlags.Public); - if (setOutMethod == null) - throw new Exception("Unable to Find Method Il2CppSystem.Console.SetOut!"); + var setOutMethod = consoleType.GetMethod("SetOut", BindingFlags.Static | BindingFlags.Public) ?? throw new Exception("Unable to Find Method Il2CppSystem.Console.SetOut!"); - setOutMethod.Invoke(null, new[] { nullStreamWriter }); + setOutMethod.Invoke(null, [nullStreamWriter]); } catch (Exception ex) { diff --git a/MelonLoader.Bootstrap/Exports.cs b/MelonLoader.Bootstrap/Exports.cs index f020dec64..04a597dc0 100644 --- a/MelonLoader.Bootstrap/Exports.cs +++ b/MelonLoader.Bootstrap/Exports.cs @@ -11,6 +11,7 @@ internal static class Exports #if WINDOWS [UnmanagedCallersOnly(EntryPoint = "DllMain")] [RequiresDynamicCode("Calls InitConfig")] + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "It's DllMain")] public static bool DllMain(nint hModule, uint ulReasonForCall, nint lpReserved) { if (ulReasonForCall != 1) diff --git a/MelonLoader/Assertions/LemonAssert.cs b/MelonLoader/Assertions/LemonAssert.cs index f0868dcc5..2bfe5186a 100644 --- a/MelonLoader/Assertions/LemonAssert.cs +++ b/MelonLoader/Assertions/LemonAssert.cs @@ -21,9 +21,9 @@ public static void IsNull(T obj, string userMessage, bool shouldThrowExceptio { var result = false; if (LemonAssertMapping.IsNull.TryGetValue(typeof(T), out var method)) - result = (bool)method.DynamicInvoke(new object[] { obj }); + result = (bool)method.DynamicInvoke([obj]); else if (LemonAssertMapping.IsNull.TryGetValue(typeof(object), out var method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj }); + result = (bool)method2.DynamicInvoke([obj]); if (!result) Failure(NullFailureMessage(true), userMessage, shouldThrowException); } @@ -36,9 +36,9 @@ public static void IsNotNull(T obj, string userMessage, bool shouldThrowExcep { var result = false; if (LemonAssertMapping.IsNull.TryGetValue(typeof(T), out var method)) - result = (bool)method.DynamicInvoke(new object[] { obj }); + result = (bool)method.DynamicInvoke([obj]); else if (LemonAssertMapping.IsNull.TryGetValue(typeof(object), out var method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj }); + result = (bool)method2.DynamicInvoke([obj]); if (result) Failure(NullFailureMessage(false), userMessage, shouldThrowException); } @@ -73,9 +73,9 @@ public static void IsEqual(T obj, T obj2, string userMessage, bool shouldThro { var result = false; if (LemonAssertMapping.IsEqual.TryGetValue(typeof(T), out var method)) - result = (bool)method.DynamicInvoke(new object[] { obj, obj2 }); + result = (bool)method.DynamicInvoke([obj, obj2]); else if (LemonAssertMapping.IsEqual.TryGetValue(typeof(object), out var method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj, obj2 }); + result = (bool)method2.DynamicInvoke([obj, obj2]); if (!result) Failure(EqualityFailureMessage(obj, obj2, true), userMessage, shouldThrowException); } @@ -88,9 +88,9 @@ public static void IsNotEqual(T obj, T obj2, string userMessage, bool shouldT { var result = false; if (LemonAssertMapping.IsEqual.TryGetValue(typeof(T), out var method)) - result = (bool)method.DynamicInvoke(new object[] { obj, obj2 }); + result = (bool)method.DynamicInvoke([obj, obj2]); else if (LemonAssertMapping.IsEqual.TryGetValue(typeof(object), out var method2)) - result = (bool)method2.DynamicInvoke(new object[] { obj, obj2 }); + result = (bool)method2.DynamicInvoke([obj, obj2]); if (result) Failure(EqualityFailureMessage(obj, obj2, false), userMessage, shouldThrowException); } diff --git a/MelonLoader/Assertions/LemonAssertException.cs b/MelonLoader/Assertions/LemonAssertException.cs index 7cfe0499d..97d547f8f 100644 --- a/MelonLoader/Assertions/LemonAssertException.cs +++ b/MelonLoader/Assertions/LemonAssertException.cs @@ -2,18 +2,13 @@ namespace MelonLoader.Assertions; -public class LemonAssertException : Exception +public class LemonAssertException(string exceptionMsg, string userMessage) : Exception(exceptionMsg) { - private readonly string UserMessage; - - public LemonAssertException(string exceptionMsg, string userMessage) : base(exceptionMsg) - => UserMessage = userMessage; - public override string Message { get { - return !string.IsNullOrEmpty(UserMessage) ? $"{base.Message}\n{UserMessage}" : base.Message; + return !string.IsNullOrEmpty(userMessage) ? $"{base.Message}\n{userMessage}" : base.Message; } } } diff --git a/MelonLoader/BackwardsCompatibility/.editorconfig b/MelonLoader/BackwardsCompatibility/.editorconfig index 479876501..ec8d422f2 100644 --- a/MelonLoader/BackwardsCompatibility/.editorconfig +++ b/MelonLoader/BackwardsCompatibility/.editorconfig @@ -1,3 +1,11 @@ [*.cs] dotnet_style_namespace_match_folder = false -dotnet_diagnostic.CA1707.severity = none \ No newline at end of file +dotnet_diagnostic.CA1707.severity = none +dotnet_style_coalesce_expression = false +dotnet_style_prefer_collection_expression = false +csharp_style_prefer_primary_constructors = false +dotnet_style_prefer_collection_expression = false +dotnet_diagnostic.IDE0060.severity = none +dotnet_diagnostic.CA1825.severity = none +dotnet_diagnostic.CA2211.severity = none +dotnet_analyzer_diagnostic.category-Style.severity = none \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs b/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs index fac163ecd..70b3a82a1 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs @@ -94,27 +94,15 @@ public class HarmonyPatchAll : HarmonyLib.HarmonyPatchAll { } [Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead.")] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class HarmonyPriority : HarmonyLib.HarmonyPriority -{ - [Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead.")] - public HarmonyPriority(int prioritiy) : base(prioritiy) { } -} +public class HarmonyPriority(int prioritiy) : HarmonyLib.HarmonyPriority(prioritiy) { } [Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead.")] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class HarmonyBefore : HarmonyLib.HarmonyBefore -{ - [Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead.")] - public HarmonyBefore(params string[] before) : base(before) { } -} +public class HarmonyBefore(params string[] before) : HarmonyLib.HarmonyBefore(before) { } [Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead.")] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class HarmonyAfter : HarmonyLib.HarmonyAfter -{ - [Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead.")] - public HarmonyAfter(params string[] after) : base(after) { } -} +public class HarmonyAfter(params string[] after) : HarmonyLib.HarmonyAfter(after) { } [Obsolete("Harmony.HarmonyPrepare is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrepare instead.")] [AttributeUsage(AttributeTargets.Method)] diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs index cfb58f0a2..03cfb326c 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs @@ -14,9 +14,8 @@ public class FastAccess [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true))")] public static InstantiationHandler CreateInstantiationHandler(Type type) { - var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null); - if (constructorInfo is null) - throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type)); + var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, [], null) + ?? throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type)); var dynamicMethod = new DynamicMethodDefinition($"InstantiateObject_{type.Name}", type, null); var generator = dynamicMethod.GetILGenerator(); generator.Emit(OpCodes.Newobj, constructorInfo); @@ -90,8 +89,8 @@ public static SetterHandler CreateSetterHandler(FieldInfo fieldInfo) } private static DynamicMethodDefinition CreateGetDynamicMethod(Type type) - => new($"DynamicGet_{type.Name}", typeof(object), new Type[] { typeof(object) }); + => new($"DynamicGet_{type.Name}", typeof(object), [typeof(object)]); private static DynamicMethodDefinition CreateSetDynamicMethod(Type type) - => new($"DynamicSet_{type.Name}", typeof(void), new Type[] { typeof(object), typeof(object) }); + => new($"DynamicSet_{type.Name}", typeof(void), [typeof(object), typeof(object)]); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs index 809ae5ae9..95cabd08e 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs @@ -5,18 +5,14 @@ namespace Harmony; -public class HarmonyInstance : HarmonyLib.Harmony +[Obsolete("Harmony.HarmonyInstance is obsolete. Please use HarmonyLib.Harmony instead.")] +public class HarmonyInstance(string id) : HarmonyLib.Harmony(id) { - [Obsolete("Harmony.HarmonyInstance is obsolete. Please use HarmonyLib.Harmony instead.")] - public HarmonyInstance(string id) : base(id) { } - - [Obsolete("Harmony.HarmonyInstance.Create is obsolete. Please use the HarmonyLib.Harmony Constructor instead.")] public static HarmonyInstance Create(string id) { return id == null ? throw new Exception("id cannot be null") : new HarmonyInstance(id); } - [Obsolete("Harmony.HarmonyInstance.Patch is obsolete. Please use HarmonyLib.Harmony.Patch instead.")] public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) { base.Patch(original, prefix, postfix, transpiler); diff --git a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs index ff3b6b192..979047be2 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs @@ -31,7 +31,7 @@ public static class HarmonyMethodExtensions [Obsolete("Harmony.HarmonyMethodExtensions.Merge is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Merge instead.")] public static HarmonyMethod Merge(this HarmonyMethod master, HarmonyMethod detail) => (HarmonyMethod)HarmonyLib.HarmonyMethodExtensions.Merge(master, detail); [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(Type) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromType instead.")] - public static List GetHarmonyMethods(this Type type) => Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromType(type).ToArray(), x => (HarmonyMethod)x).ToList(); + public static List GetHarmonyMethods(this Type type) => [.. Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromType(type).ToArray(), x => (HarmonyMethod)x)]; [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(MethodBase) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromMethod instead.")] - public static List GetHarmonyMethods(this MethodBase method) => Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromMethod(method).ToArray(), x => (HarmonyMethod)x).ToList(); + public static List GetHarmonyMethods(this MethodBase method) => [.. Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromMethod(method).ToArray(), x => (HarmonyMethod)x)]; } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs b/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs index 4142d3645..9c8ef3df6 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs @@ -27,16 +27,11 @@ public class PatchInfo : HarmonyLib.PatchInfo { } [Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead.")] [Serializable] -public class Patch : IComparable +public class Patch(MethodInfo patch, int index, string owner, int priority, string[] before, string[] after) : IComparable { - public readonly MethodInfo patch; - private readonly HarmonyLib.Patch patchWrapper; - [Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead.")] - public Patch(MethodInfo patch, int index, string owner, int priority, string[] before, string[] after) - { - this.patch = patch; - patchWrapper = new HarmonyLib.Patch(patch, index, owner, priority, before, after, false); - } + public readonly MethodInfo patch = patch; + private readonly HarmonyLib.Patch patchWrapper = new(patch, index, owner, priority, before, after, false); + public MethodInfo GetMethod(MethodBase original) => patchWrapper.GetMethod(original); public override bool Equals(object obj) => patchWrapper.Equals(obj); public int CompareTo(object obj) => patchWrapper.CompareTo(obj); diff --git a/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs index 02ccc48be..174a1a383 100644 --- a/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarExtendedHeaderReader.cs @@ -28,7 +28,7 @@ public class TarExtendedHeaderReader private int state = LENGTH; - private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' }; + private static readonly byte[] StateNext = [(byte)' ', (byte)'=', (byte)'\n']; /// /// Creates a new . diff --git a/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs index c8c79c565..233f70b0a 100644 --- a/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs +++ b/MelonLoader/BackwardsCompatibility/ICSharpCode/SharpZipLib/Tar/TarInputStream.cs @@ -506,7 +506,7 @@ public TarEntry GetNextEntry() throw new InvalidHeaderException("Failed to read long name entry"); } - longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead, encoding).ToString()); + longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead, encoding)); numToRead -= numRead; } diff --git a/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs b/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs index 2500a98d6..1f42265ec 100644 --- a/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs +++ b/MelonLoader/CoreClrUtils/CoreClrDelegateFixer.cs @@ -79,9 +79,9 @@ private static Type GetHookWrapperDelegateType(MelonBase melon, MethodInfo manag return ret; var type = module.DefineType(typeName, TypeAttributes.Sealed | TypeAttributes.Public, typeof(MulticastDelegate)); - type.SetCustomAttribute(new CustomAttributeBuilder(typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new[] { typeof(CallingConvention) }), new object[] { CallingConvention.Cdecl })); + type.SetCustomAttribute(new CustomAttributeBuilder(typeof(UnmanagedFunctionPointerAttribute).GetConstructor([typeof(CallingConvention)]), [CallingConvention.Cdecl])); - var ctor = type.DefineConstructor(MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Public, CallingConventions.HasThis, new[] { typeof(object), typeof(IntPtr) }); + var ctor = type.DefineConstructor(MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Public, CallingConventions.HasThis, [typeof(object), typeof(IntPtr)]); ctor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask); var parameterTypes = managedMethod.GetParameters().Select(p => p.ParameterType).ToArray(); @@ -99,7 +99,7 @@ private static Type GetHookWrapperDelegateType(MelonBase melon, MethodInfo manag "BeginInvoke", MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public, CallingConventions.HasThis, typeof(IAsyncResult), - parameterTypes.Concat(new[] { typeof(AsyncCallback), typeof(object) }).ToArray() + [.. parameterTypes, typeof(AsyncCallback), typeof(object)] ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask); type.DefineMethod( @@ -107,7 +107,7 @@ private static Type GetHookWrapperDelegateType(MelonBase melon, MethodInfo manag MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.Public, CallingConventions.HasThis, managedMethod.ReturnType, - new[] { typeof(IAsyncResult) } + [typeof(IAsyncResult)] ).SetImplementationFlags(MethodImplAttributes.CodeTypeMask); return type.CreateType(); diff --git a/MelonLoader/CoreClrUtils/MethodBaseHelper.cs b/MelonLoader/CoreClrUtils/MethodBaseHelper.cs index ceb1ad200..54d087214 100644 --- a/MelonLoader/CoreClrUtils/MethodBaseHelper.cs +++ b/MelonLoader/CoreClrUtils/MethodBaseHelper.cs @@ -23,7 +23,7 @@ internal static class MethodBaseHelper ( BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DoNotWrapExceptions, binder: null, - new[] { typeof(IntPtr) }, + [typeof(IntPtr)], modifiers: null ) ?? throw new InvalidOperationException("RuntimeMethodHandleInternal constructor is missing!"); @@ -33,13 +33,13 @@ internal static class MethodBaseHelper "GetMethodBase", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DoNotWrapExceptions, binder: null, - new[] { RuntimeType, RuntimeMethodHandleInternal }, + [RuntimeType, RuntimeMethodHandleInternal], modifiers: null ) ?? throw new InvalidOperationException("RuntimeType.GetMethodBase is missing!"); // Wrap the handle - var runtimeHandle = RuntimeMethodHandleInternal_Constructor.Invoke(new[] { (object)handle }); - return (MethodBase?)RuntimeType_GetMethodBase.Invoke(null, new[] { null, runtimeHandle }); + var runtimeHandle = RuntimeMethodHandleInternal_Constructor.Invoke([handle]); + return (MethodBase?)RuntimeType_GetMethodBase.Invoke(null, [null, runtimeHandle]); } } #endif \ No newline at end of file diff --git a/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs b/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs index 27edbe1a4..9669595c1 100644 --- a/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs +++ b/MelonLoader/Fixes/DotnetAssemblyLoadContextFix.cs @@ -26,7 +26,7 @@ internal static void Install() { try { - Core.HarmonyInstance.Patch(AccessTools.Method(typeof(Assembly), nameof(Assembly.Load), new Type[] { typeof(byte[]), typeof(byte[]) }), new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAssemblyLoad))); + Core.HarmonyInstance.Patch(AccessTools.Method(typeof(Assembly), nameof(Assembly.Load), [typeof(byte[]), typeof(byte[])]), new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAssemblyLoad))); Core.HarmonyInstance.Patch(AccessTools.Method(typeof(Assembly), nameof(Assembly.LoadFile)), new HarmonyMethod(typeof(DotnetAssemblyLoadContextFix), nameof(PreAssemblyLoadFile))); //We have to load everything required for the verifier to avoid getting stuck in an infinite loop, prior to hooking AssemblyLoadContext. diff --git a/MelonLoader/Fixes/Il2CppICallInjector.cs b/MelonLoader/Fixes/Il2CppICallInjector.cs index 187e778d7..c459bae78 100644 --- a/MelonLoader/Fixes/Il2CppICallInjector.cs +++ b/MelonLoader/Fixes/Il2CppICallInjector.cs @@ -45,9 +45,7 @@ internal static unsafe void Install() throw new Exception("Failed to get Il2CppDetourMethodPatcher.GenerateNativeToManagedTrampoline"); var gameAssemblyName = "GameAssembly"; - var gameAssemblyLib = NativeLibrary.Load(gameAssemblyName); - if (gameAssemblyLib == null) - throw new Exception($"Failed to load {gameAssemblyName} Native Library"); + var gameAssemblyLib = NativeLibrary.Load(gameAssemblyName) ?? throw new Exception($"Failed to load {gameAssemblyName} Native Library"); IntPtr il2cpp_resolve_icall = gameAssemblyLib.GetExport(nameof(il2cpp_resolve_icall)); if (il2cpp_resolve_icall == IntPtr.Zero) @@ -58,7 +56,7 @@ internal static unsafe void Install() throw new Exception($"Failed to get {nameof(il2cpp_add_internal_call)} Native Export"); MelonDebug.Msg("Patching il2cpp_resolve_icall..."); - var detourPtr = Marshal.GetFunctionPointerForDelegate((dil2cpp_resolve_icall)il2cpp_resolve_icall_Detour); + var detourPtr = Marshal.GetFunctionPointerForDelegate((dil2cpp_resolve_icall)Il2cppResolveICallDetour); il2cpp_resolve_icall_hook = new NativeHook(il2cpp_resolve_icall, detourPtr); il2cpp_resolve_icall_hook.Attach(); } @@ -104,7 +102,7 @@ private static void LogDebugWarning(string msg) _logger.Warning(msg); } - private static IntPtr il2cpp_resolve_icall_Detour(IntPtr signature) + private static IntPtr Il2cppResolveICallDetour(IntPtr signature) { // Convert Pointer to String var signatureStr = Marshal.PtrToStringAnsi(signature); diff --git a/MelonLoader/Fixes/Il2CppInteropFixes.cs b/MelonLoader/Fixes/Il2CppInteropFixes.cs index 86bf82302..d9eb3ecea 100644 --- a/MelonLoader/Fixes/Il2CppInteropFixes.cs +++ b/MelonLoader/Fixes/Il2CppInteropFixes.cs @@ -92,13 +92,9 @@ internal static void Install() var il2cppType = typeof(IL2CPP); var harmonySupportType = typeof(HarmonySupport); - var injectorHelpersType = classInjectorType.Assembly.GetType("Il2CppInterop.Runtime.Injection.InjectorHelpers"); - if (injectorHelpersType == null) - throw new Exception("Failed to get InjectorHelpers"); + var injectorHelpersType = classInjectorType.Assembly.GetType("Il2CppInterop.Runtime.Injection.InjectorHelpers") ?? throw new Exception("Failed to get InjectorHelpers"); - var detourMethodPatcherType = harmonySupportType.Assembly.GetType("Il2CppInterop.HarmonySupport.Il2CppDetourMethodPatcher"); - if (detourMethodPatcherType == null) - throw new Exception("Failed to get Il2CppDetourMethodPatcher"); + var detourMethodPatcherType = harmonySupportType.Assembly.GetType("Il2CppInterop.HarmonySupport.Il2CppDetourMethodPatcher") ?? throw new Exception("Failed to get Il2CppDetourMethodPatcher"); _systemTypeFromIl2CppType = classInjectorType.GetMethod("SystemTypeFromIl2CppType", BindingFlags.NonPublic | BindingFlags.Static); if (_systemTypeFromIl2CppType == null) @@ -413,8 +409,7 @@ private static bool RewriteGlobalContext_TryGetNewTypeForOriginal_Prefix(Rewrite if (string.IsNullOrEmpty(assemblyName)) return false; - AssemblyRewriteContext rewriteContext; - if (contexts.TryGetValue(assemblyName, out rewriteContext)) + if (contexts.TryGetValue(assemblyName, out var rewriteContext)) { //LogDebugMsg($"[RewriteGlobalContext] Found: {assemblyName}"); __result = rewriteContext.TryGetContextForOriginalType(__0); diff --git a/MelonLoader/IniFile.cs b/MelonLoader/IniFile.cs index 6f006ae6c..428dad4ef 100644 --- a/MelonLoader/IniFile.cs +++ b/MelonLoader/IniFile.cs @@ -8,9 +8,12 @@ public class IniFile { [DllImport("KERNEL32.DLL", EntryPoint = "GetPrivateProfileStringW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern int GetPrivateProfileString(string lpSection, string lpKey, string lpDefault, StringBuilder lpReturnString, int nSize, string lpFileName); + [DllImport("KERNEL32.DLL", EntryPoint = "WritePrivateProfileStringW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern int WritePrivateProfileString(string lpSection, string lpKey, string lpValue, string lpFileName); + private string _path = ""; + public string Path { get { return _path; } @@ -21,8 +24,12 @@ internal set _path = value; } } + public IniFile(string INIPath) { Path = INIPath; } - private void WriteValue(string Section, string Key, string Value) { WritePrivateProfileString(Section, Key, Value, Path); } + + private void WriteValue(string Section, string Key, string Value) + => _ = WritePrivateProfileString(Section, Key, Value, Path); + private string ReadValue(string Section, string Key) { const int MAX_CHARS = 1023; @@ -31,7 +38,8 @@ private string ReadValue(string Section, string Key) return result.ToString().Equals(" _") ? null : result.ToString(); } - public bool HasKey(string section, string name) { return ReadValue(section, name) != null; } + public bool HasKey(string section, string name) + => ReadValue(section, name) != null; public string GetString(string section, string name, string defaultValue = "", bool autoSave = false) { @@ -42,7 +50,9 @@ public string GetString(string section, string name, string defaultValue = "", b SetString(section, name, defaultValue); return defaultValue; } - public void SetString(string section, string name, string value) { WriteValue(section, name, value.Trim()); } + + public void SetString(string section, string name, string value) + => WriteValue(section, name, value.Trim()); public int GetInt(string section, string name, int defaultValue = 0, bool autoSave = false) { @@ -52,7 +62,9 @@ public int GetInt(string section, string name, int defaultValue = 0, bool autoSa SetInt(section, name, defaultValue); return defaultValue; } - public void SetInt(string section, string name, int value) { WriteValue(section, name, value.ToString()); } + + public void SetInt(string section, string name, int value) + => WriteValue(section, name, value.ToString()); public float GetFloat(string section, string name, float defaultValue = 0f, bool autoSave = false) { @@ -62,7 +74,9 @@ public float GetFloat(string section, string name, float defaultValue = 0f, bool SetFloat(section, name, defaultValue); return defaultValue; } - public void SetFloat(string section, string name, float value) { WriteValue(section, name, value.ToString()); } + + public void SetFloat(string section, string name, float value) + => WriteValue(section, name, value.ToString()); public bool GetBool(string section, string name, bool defaultValue = false, bool autoSave = false) { @@ -73,5 +87,7 @@ public bool GetBool(string section, string name, bool defaultValue = false, bool SetBool(section, name, defaultValue); return defaultValue; } - public void SetBool(string section, string name, bool value) { WriteValue(section, name, value ? "true" : "false"); } + + public void SetBool(string section, string name, bool value) + => WriteValue(section, name, value ? "true" : "false"); } \ No newline at end of file diff --git a/MelonLoader/InternalUtils/DependencyGraph.cs b/MelonLoader/InternalUtils/DependencyGraph.cs index 7a984937b..ffc71664a 100644 --- a/MelonLoader/InternalUtils/DependencyGraph.cs +++ b/MelonLoader/InternalUtils/DependencyGraph.cs @@ -119,11 +119,11 @@ private DependencyGraph(IList melons) if ((missingDependencies.Count > 0) && !melonsWithMissingDeps.ContainsKey(melonVertex.melon.Info.Name)) // melonVertex.skipLoading = true; - melonsWithMissingDeps.Add(melonVertex.melon.Info.Name, missingDependencies.ToArray()); + melonsWithMissingDeps.Add(melonVertex.melon.Info.Name, [.. missingDependencies]); if ((incompatibilities.Count > 0) && !melonsWithIncompatibilities.ContainsKey(melonVertex.melon.Info.Name)) - melonsWithIncompatibilities.Add(melonVertex.melon.Info.Name, incompatibilities.ToArray()); + melonsWithIncompatibilities.Add(melonVertex.melon.Info.Name, [.. incompatibilities]); } // Some Melons are missing dependencies. Don't load these Melons and show an error message diff --git a/MelonLoader/InternalUtils/UnityMagicMethods.cs b/MelonLoader/InternalUtils/UnityMagicMethods.cs index 7ae955a51..27cc028ae 100644 --- a/MelonLoader/InternalUtils/UnityMagicMethods.cs +++ b/MelonLoader/InternalUtils/UnityMagicMethods.cs @@ -32,79 +32,79 @@ static UnityMagicMethods() MonoBehaviourMethods = new Dictionary { - ["Awake"] = new Type[0], - ["FixedUpdate"] = new Type[0], - ["LateUpdate"] = new Type[0], - ["OnAnimatorIK"] = new[] { typeof(int) }, - ["OnAnimatorMove"] = new Type[0], - ["OnApplicationFocus"] = new[] { typeof(bool) }, - ["OnApplicationPause"] = new[] { typeof(bool) }, - ["OnApplicationQuit"] = new Type[0], - ["OnAudioFilterRead"] = new[] { typeof(float[]), typeof(int) }, - ["OnBecameInvisible"] = new Type[0], - ["OnBecameVisible"] = new Type[0], - ["OnCollisionEnter"] = new[] { unityType("UnityEngine.Collision") }, - ["OnCollisionEnter2D"] = new[] { unityType("UnityEngine.Collision2D") }, - ["OnCollisionExit"] = new[] { unityType("UnityEngine.Collision") }, - ["OnCollisionExit2D"] = new[] { unityType("UnityEngine.Collision2D") }, - ["OnCollisionStay"] = new[] { unityType("UnityEngine.Collision") }, - ["OnCollisionStay2D"] = new[] { unityType("UnityEngine.Collision2D") }, - ["OnConnectedToServer"] = new Type[0], - ["OnControllerColliderHit"] = new[] { unityType("UnityEngine.ControllerColliderHit") }, - ["OnDestroy"] = new Type[0], - ["OnDisable"] = new Type[0], - ["OnDisconnectedFromServer"] = new[] { unityType("UnityEngine.NetworkDisconnection") }, + ["Awake"] = [], + ["FixedUpdate"] = [], + ["LateUpdate"] = [], + ["OnAnimatorIK"] = [typeof(int)], + ["OnAnimatorMove"] = [], + ["OnApplicationFocus"] = [typeof(bool)], + ["OnApplicationPause"] = [typeof(bool)], + ["OnApplicationQuit"] = [], + ["OnAudioFilterRead"] = [typeof(float[]), typeof(int)], + ["OnBecameInvisible"] = [], + ["OnBecameVisible"] = [], + ["OnCollisionEnter"] = [unityType("UnityEngine.Collision")], + ["OnCollisionEnter2D"] = [unityType("UnityEngine.Collision2D")], + ["OnCollisionExit"] = [unityType("UnityEngine.Collision")], + ["OnCollisionExit2D"] = [unityType("UnityEngine.Collision2D")], + ["OnCollisionStay"] = [unityType("UnityEngine.Collision")], + ["OnCollisionStay2D"] = [unityType("UnityEngine.Collision2D")], + ["OnConnectedToServer"] = [], + ["OnControllerColliderHit"] = [unityType("UnityEngine.ControllerColliderHit")], + ["OnDestroy"] = [], + ["OnDisable"] = [], + ["OnDisconnectedFromServer"] = [unityType("UnityEngine.NetworkDisconnection")], // "OnDrawGizmos" is editor-only // "OnDrawGizmosSelected" is editor-only - ["OnEnable"] = new Type[0], - ["OnFailedToConnect"] = new[] { unityType("UnityEngine.NetworkConnectionError") }, - ["OnFailedToConnectToMasterServer"] = new[] { unityType("UnityEngine.NetworkConnectionError") }, - ["OnGUI"] = new Type[0], - ["OnJointBreak"] = new[] { typeof(float) }, - ["OnJointBreak2D"] = new[] { unityType("UnityEngine.Joint2D") }, - ["OnMasterServerEvent"] = new[] { unityType("UnityEngine.MasterServerEvent") }, - ["OnMouseDown"] = new Type[0], - ["OnMouseDrag"] = new Type[0], - ["OnMouseEnter"] = new Type[0], - ["OnMouseExit"] = new Type[0], - ["OnMouseOver"] = new Type[0], - ["OnMouseUp"] = new Type[0], - ["OnMouseUpAsButton"] = new Type[0], - ["OnNetworkInstantiate"] = new[] { unityType("UnityEngine.NetworkMessageInfo") }, - ["OnParticleCollision"] = new[] { unityType("UnityEngine.GameObject") }, - ["OnParticleSystemStopped"] = new Type[0], - ["OnParticleTrigger"] = new Type[0], - ["OnParticleUpdateJobScheduled"] = new Type[0], - ["OnPlayerConnected"] = new[] { unityType("UnityEngine.NetworkPlayer") }, - ["OnPlayerDisconnected"] = new[] { unityType("UnityEngine.NetworkPlayer") }, - ["OnPostRender"] = new Type[0], - ["OnPreCull"] = new Type[0], - ["OnPreRender"] = new Type[0], - ["OnRenderImage"] = new[] { unityType("UnityEngine.RenderTexture"), unityType("UnityEngine.RenderTexture") }, - ["OnRenderObject"] = new Type[0], - ["OnSerializeNetworkView"] = new[] { unityType("UnityEngine.BitStream"), unityType("UnityEngine.NetworkMessageInfo") }, - ["OnServerInitialized"] = new Type[0], - ["OnTransformChildrenChanged"] = new Type[0], - ["OnTransformParentChanged"] = new Type[0], - ["OnTriggerEnter"] = new[] { unityType("UnityEngine.Collider") }, - ["OnTriggerEnter2D"] = new[] { unityType("UnityEngine.Collider2D") }, - ["OnTriggerExit"] = new[] { unityType("UnityEngine.Collider") }, - ["OnTriggerExit2D"] = new[] { unityType("UnityEngine.Collider2D") }, - ["OnTriggerStay"] = new[] { unityType("UnityEngine.Collider") }, - ["OnTriggerStay2D"] = new[] { unityType("UnityEngine.Collider2D") }, + ["OnEnable"] = [], + ["OnFailedToConnect"] = [unityType("UnityEngine.NetworkConnectionError")], + ["OnFailedToConnectToMasterServer"] = [unityType("UnityEngine.NetworkConnectionError")], + ["OnGUI"] = [], + ["OnJointBreak"] = [typeof(float)], + ["OnJointBreak2D"] = [unityType("UnityEngine.Joint2D")], + ["OnMasterServerEvent"] = [unityType("UnityEngine.MasterServerEvent")], + ["OnMouseDown"] = [], + ["OnMouseDrag"] = [], + ["OnMouseEnter"] = [], + ["OnMouseExit"] = [], + ["OnMouseOver"] = [], + ["OnMouseUp"] = [], + ["OnMouseUpAsButton"] = [], + ["OnNetworkInstantiate"] = [unityType("UnityEngine.NetworkMessageInfo")], + ["OnParticleCollision"] = [unityType("UnityEngine.GameObject")], + ["OnParticleSystemStopped"] = [], + ["OnParticleTrigger"] = [], + ["OnParticleUpdateJobScheduled"] = [], + ["OnPlayerConnected"] = [unityType("UnityEngine.NetworkPlayer")], + ["OnPlayerDisconnected"] = [unityType("UnityEngine.NetworkPlayer")], + ["OnPostRender"] = [], + ["OnPreCull"] = [], + ["OnPreRender"] = [], + ["OnRenderImage"] = [unityType("UnityEngine.RenderTexture"), unityType("UnityEngine.RenderTexture")], + ["OnRenderObject"] = [], + ["OnSerializeNetworkView"] = [unityType("UnityEngine.BitStream"), unityType("UnityEngine.NetworkMessageInfo")], + ["OnServerInitialized"] = [], + ["OnTransformChildrenChanged"] = [], + ["OnTransformParentChanged"] = [], + ["OnTriggerEnter"] = [unityType("UnityEngine.Collider")], + ["OnTriggerEnter2D"] = [unityType("UnityEngine.Collider2D")], + ["OnTriggerExit"] = [unityType("UnityEngine.Collider")], + ["OnTriggerExit2D"] = [unityType("UnityEngine.Collider2D")], + ["OnTriggerStay"] = [unityType("UnityEngine.Collider")], + ["OnTriggerStay2D"] = [unityType("UnityEngine.Collider2D")], // "OnValidate" is editor-only - ["OnWillRenderObject"] = new Type[0], + ["OnWillRenderObject"] = [], // "Reset" is editor-only - ["Start"] = new Type[0], - ["Update"] = new Type[0], + ["Start"] = [], + ["Update"] = [], }; ScriptableObjectMethods = new Dictionary { - ["Awake"] = new Type[0], - ["OnDestroy"] = new Type[0], - ["OnDisable"] = new Type[0], - ["OnEnable"] = new Type[0], + ["Awake"] = [], + ["OnDestroy"] = [], + ["OnDisable"] = [], + ["OnEnable"] = [], }; } diff --git a/MelonLoader/InteropSupport.cs b/MelonLoader/InteropSupport.cs index 3fea56d95..bb4ee5916 100644 --- a/MelonLoader/InteropSupport.cs +++ b/MelonLoader/InteropSupport.cs @@ -1,10 +1,12 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace MelonLoader; public static class InteropSupport { + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Who named this")] public interface Interface { bool IsInheritedFromIl2CppObjectBase(Type type); @@ -73,7 +75,7 @@ public static T Il2CppObjectPtrToIl2CppObject(IntPtr ptr) ? throw new NullReferenceException("The ptr cannot be IntPtr.Zero.") : !IsGeneratedAssemblyType(typeof(T)) ? throw new NullReferenceException("The type must be a Generated Assembly Type.") - : (T)typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(IntPtr) }, new ParameterModifier[0]).Invoke(new object[] { ptr }); + : (T)typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, [typeof(IntPtr)], []).Invoke([ptr]); } public static int? GetIl2CppMethodCallerCount(MethodBase method) diff --git a/MelonLoader/LemonArraySegment.cs b/MelonLoader/LemonArraySegment.cs index 3a2c37813..88f49cc4e 100644 --- a/MelonLoader/LemonArraySegment.cs +++ b/MelonLoader/LemonArraySegment.cs @@ -72,7 +72,7 @@ public override int GetHashCode() /// /// if the specified object is a structure and is equal to the current instance; otherwise, . public override bool Equals(object obj) - => obj is LemonArraySegment && Equals((LemonArraySegment)obj); + => obj is LemonArraySegment segment && Equals(segment); /// Determines whether the specified structure is equal to the current instance. /// The structure to compare with the current instance. diff --git a/MelonLoader/LemonEnumerator.cs b/MelonLoader/LemonEnumerator.cs index 37be1791b..569b0d062 100644 --- a/MelonLoader/LemonEnumerator.cs +++ b/MelonLoader/LemonEnumerator.cs @@ -14,13 +14,13 @@ public class LemonEnumerator : IEnumerator, IEnumerable /// Creates a new instance of with a new copy of ''. /// public LemonEnumerator(T[] lemons) - => LemonPatch = lemons.ToArray(); + => LemonPatch = [.. lemons]; /// /// Creates a new instance of with a new copy of ''. /// public LemonEnumerator(IList lemons) - => LemonPatch = lemons.ToArray(); + => LemonPatch = [.. lemons]; object IEnumerator.Current => Current; public T Current { get; private set; } diff --git a/MelonLoader/LoaderConfig.cs b/MelonLoader/LoaderConfig.cs index 334b49975..bb11f573c 100644 --- a/MelonLoader/LoaderConfig.cs +++ b/MelonLoader/LoaderConfig.cs @@ -1,8 +1,13 @@ -using System.Diagnostics; -using System.IO; +using System.IO; using System.Runtime.InteropServices; using Tomlet.Attributes; +#if NET6_0_OR_GREATER +using System; +#else +using System.Diagnostics; +#endif + namespace MelonLoader; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] @@ -26,7 +31,13 @@ public class LoaderConfig public class CoreConfig { [TomlNonSerialized] - public string BaseDirectory { get; internal set; } = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule!.FileName)!; + public string BaseDirectory { get; internal set; } = Path.GetDirectoryName( +#if NET6_0_OR_GREATER + Environment.ProcessPath +#else + Process.GetCurrentProcess().MainModule!.FileName +#endif + )!; // Technically, this will always return false, but it's still a config ¯\_(ツ)_/¯ [TomlProperty("disable")] diff --git a/MelonLoader/MelonAdditionalCreditsAttribute.cs b/MelonLoader/MelonAdditionalCreditsAttribute.cs index 85f4cc818..509a7a85d 100644 --- a/MelonLoader/MelonAdditionalCreditsAttribute.cs +++ b/MelonLoader/MelonAdditionalCreditsAttribute.cs @@ -1,20 +1,15 @@ using System; namespace MelonLoader; +/// +/// AdditionalCredits constructor +/// +/// The additional credits of the mod [AttributeUsage(AttributeTargets.Assembly)] -public class MelonAdditionalCreditsAttribute : Attribute +public class MelonAdditionalCreditsAttribute(string credits) : Attribute { /// /// Any additional credits that the mod author might want to include /// - public string Credits { get; internal set; } - - /// - /// AdditionalCredits constructor - /// - /// The additional credits of the mod - public MelonAdditionalCreditsAttribute(string credits) - { - Credits = credits; - } + public string Credits { get; internal set; } = credits; } diff --git a/MelonLoader/MelonAdditionalDependenciesAttribute.cs b/MelonLoader/MelonAdditionalDependenciesAttribute.cs index 7a7968288..aa1284f21 100644 --- a/MelonLoader/MelonAdditionalDependenciesAttribute.cs +++ b/MelonLoader/MelonAdditionalDependenciesAttribute.cs @@ -3,12 +3,10 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class MelonAdditionalDependenciesAttribute : Attribute +public class MelonAdditionalDependenciesAttribute(params string[] assemblyNames) : Attribute { /// /// The (simple) assembly names of Additional Dependencies that aren't directly referenced but should still be regarded as important. /// - public string[] AssemblyNames { get; internal set; } - - public MelonAdditionalDependenciesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } + public string[] AssemblyNames { get; internal set; } = assemblyNames; } \ No newline at end of file diff --git a/MelonLoader/MelonEvent.cs b/MelonLoader/MelonEvent.cs index 6a861af5c..75d127a65 100644 --- a/MelonLoader/MelonEvent.cs +++ b/MelonLoader/MelonEvent.cs @@ -4,19 +4,14 @@ namespace MelonLoader; -public abstract class MelonEventBase where T : Delegate +public abstract class MelonEventBase(bool oneTimeUse = false) where T : Delegate { private readonly List> actions = []; - private MelonAction[] cachedActionsArray = new MelonAction[0]; - public readonly bool oneTimeUse; + private MelonAction[] cachedActionsArray = []; + public readonly bool oneTimeUse = oneTimeUse; public bool Disposed { get; private set; } - public MelonEventBase(bool oneTimeUse = false) - { - this.oneTimeUse = oneTimeUse; - } - public bool CheckIfSubscribed(MethodInfo method, object obj = null) { lock (actions) @@ -107,7 +102,7 @@ public void Unsubscribe(MethodInfo method, object obj = null) private void UpdateEnumerator() { - cachedActionsArray = actions.ToArray(); + cachedActionsArray = [.. actions]; } public class MelonEventSubscriber @@ -128,7 +123,7 @@ public MelonEventSubscriber[] GetSubscribers() priority = act.priority, melonAssembly = act.melonAssembly }); - return allSubs.ToArray(); + return [.. allSubs]; } protected void Invoke(Action delegateInvoker) @@ -168,79 +163,72 @@ public void Dispose() } #region Param Children -public class MelonEvent : MelonEventBase +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase(oneTimeUse) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } - public void Invoke() { Invoke(x => x()); } } -public class MelonEvent : MelonEventBase> -{ - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) +{ public void Invoke(T1 arg1) { Invoke(x => x(arg1)); } } -public class MelonEvent : MelonEventBase> -{ - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) +{ public void Invoke(T1 arg1, T2 arg2) { Invoke(x => x(arg1, arg2)); } } -public class MelonEvent : MelonEventBase> -{ - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) +{ public void Invoke(T1 arg1, T2 arg2, T3 arg3) { Invoke(x => x(arg1, arg2, arg3)); } } -public class MelonEvent : MelonEventBase> -{ - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) +{ public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { Invoke(x => x(arg1, arg2, arg3, arg4)); } } -public class MelonEvent : MelonEventBase> -{ - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) +{ public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { Invoke(x => x(arg1, arg2, arg3, arg4, arg5)); } } -public class MelonEvent : MelonEventBase> + +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6)); } } -public class MelonEvent : MelonEventBase> + +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6, arg7)); } } -public class MelonEvent : MelonEventBase> + +public class MelonEvent(bool oneTimeUse = false) : MelonEventBase>(oneTimeUse) { - public MelonEvent(bool oneTimeUse = false) : base(oneTimeUse) { } public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { Invoke(x => x(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); diff --git a/MelonLoader/MelonGameAttribute.cs b/MelonLoader/MelonGameAttribute.cs index 5ddca6e1a..7e65eb095 100644 --- a/MelonLoader/MelonGameAttribute.cs +++ b/MelonLoader/MelonGameAttribute.cs @@ -3,23 +3,18 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] -public class MelonGameAttribute : Attribute +public class MelonGameAttribute(string developer = null, string name = null) : Attribute { - public MelonGameAttribute(string developer = null, string name = null) - { - Developer = developer; - Name = name; - } /// /// Developer of the Game. /// - public string Developer { get; internal set; } + public string Developer { get; internal set; } = developer; /// /// Name of the Game. /// - public string Name { get; internal set; } + public string Name { get; internal set; } = name; /// /// If the Attribute is set as Universal or not. diff --git a/MelonLoader/MelonGameVersionAttribute.cs b/MelonLoader/MelonGameVersionAttribute.cs index 0b65e5695..e6d513997 100644 --- a/MelonLoader/MelonGameVersionAttribute.cs +++ b/MelonLoader/MelonGameVersionAttribute.cs @@ -3,15 +3,13 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] -public class MelonGameVersionAttribute : Attribute +public class MelonGameVersionAttribute(string version = null) : Attribute { - public MelonGameVersionAttribute(string version = null) - => Version = version; /// /// Version of the Game. /// - public string Version { get; internal set; } + public string Version { get; internal set; } = version; /// /// If the Attribute is set as Universal or not. diff --git a/MelonLoader/MelonIncompatibleAssembliesAttribute.cs b/MelonLoader/MelonIncompatibleAssembliesAttribute.cs index 0ac194ea4..415170043 100644 --- a/MelonLoader/MelonIncompatibleAssembliesAttribute.cs +++ b/MelonLoader/MelonIncompatibleAssembliesAttribute.cs @@ -3,12 +3,10 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class MelonIncompatibleAssembliesAttribute : Attribute +public class MelonIncompatibleAssembliesAttribute(params string[] assemblyNames) : Attribute { /// /// The (simple) assembly names of the mods that are incompatible. /// - public string[] AssemblyNames { get; internal set; } - - public MelonIncompatibleAssembliesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } + public string[] AssemblyNames { get; internal set; } = assemblyNames; } \ No newline at end of file diff --git a/MelonLoader/MelonMod.cs b/MelonLoader/MelonMod.cs index 8052d0c9c..c8d3f5dd3 100644 --- a/MelonLoader/MelonMod.cs +++ b/MelonLoader/MelonMod.cs @@ -101,7 +101,7 @@ public MelonModGameAttribute[] GameAttributes List newatts = []; foreach (var att in Games) newatts.Add(new MelonModGameAttribute(att.Developer, att.Name)); - _LegacyGameAttributes = newatts.ToArray(); + _LegacyGameAttributes = [.. newatts]; return _LegacyGameAttributes; } } diff --git a/MelonLoader/MelonOptionalDependenciesAttribute.cs b/MelonLoader/MelonOptionalDependenciesAttribute.cs index cbf126524..184301e69 100644 --- a/MelonLoader/MelonOptionalDependenciesAttribute.cs +++ b/MelonLoader/MelonOptionalDependenciesAttribute.cs @@ -3,12 +3,10 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class MelonOptionalDependenciesAttribute : Attribute +public class MelonOptionalDependenciesAttribute(params string[] assemblyNames) : Attribute { /// /// The (simple) assembly names of the dependencies that should be regarded as optional. /// - public string[] AssemblyNames { get; internal set; } - - public MelonOptionalDependenciesAttribute(params string[] assemblyNames) { AssemblyNames = assemblyNames; } + public string[] AssemblyNames { get; internal set; } = assemblyNames; } \ No newline at end of file diff --git a/MelonLoader/MelonPlatformDomainAttribute.cs b/MelonLoader/MelonPlatformDomainAttribute.cs index b360c13fc..e8ed73228 100644 --- a/MelonLoader/MelonPlatformDomainAttribute.cs +++ b/MelonLoader/MelonPlatformDomainAttribute.cs @@ -3,10 +3,8 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class MelonPlatformDomainAttribute : Attribute +public class MelonPlatformDomainAttribute(MelonPlatformDomainAttribute.CompatibleDomains domain = MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL) : Attribute { - public MelonPlatformDomainAttribute(CompatibleDomains domain = CompatibleDomains.UNIVERSAL) => Domain = domain; - // Enum for Melon Platform Domain Compatibility. public enum CompatibleDomains { @@ -16,7 +14,7 @@ public enum CompatibleDomains }; // Platform Domain Compatibility of the Melon. - public CompatibleDomains Domain { get; internal set; } + public CompatibleDomains Domain { get; internal set; } = domain; public bool IsCompatible(CompatibleDomains domain) => Domain == CompatibleDomains.UNIVERSAL || domain == CompatibleDomains.UNIVERSAL || Domain == domain; diff --git a/MelonLoader/MelonPlugin.cs b/MelonLoader/MelonPlugin.cs index cada90e57..0cb9d97ae 100644 --- a/MelonLoader/MelonPlugin.cs +++ b/MelonLoader/MelonPlugin.cs @@ -90,7 +90,7 @@ public MelonPluginGameAttribute[] GameAttributes List newatts = []; foreach (var att in Games) newatts.Add(new MelonPluginGameAttribute(att.Developer, att.Name)); - _LegacyGameAttributes = newatts.ToArray(); + _LegacyGameAttributes = [.. newatts]; return _LegacyGameAttributes; } } diff --git a/MelonLoader/MelonPriorityAttribute.cs b/MelonLoader/MelonPriorityAttribute.cs index e04066bfe..57d1dfed6 100644 --- a/MelonLoader/MelonPriorityAttribute.cs +++ b/MelonLoader/MelonPriorityAttribute.cs @@ -3,12 +3,10 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class MelonPriorityAttribute : Attribute +public class MelonPriorityAttribute(int priority = 0) : Attribute { /// /// Priority of the Melon. /// - public int Priority; - - public MelonPriorityAttribute(int priority = 0) => Priority = priority; + public int Priority = priority; } \ No newline at end of file diff --git a/MelonLoader/NativeUtils/CppUtils.cs b/MelonLoader/NativeUtils/CppUtils.cs index efc98d1cb..81075f985 100644 --- a/MelonLoader/NativeUtils/CppUtils.cs +++ b/MelonLoader/NativeUtils/CppUtils.cs @@ -92,7 +92,7 @@ public static unsafe IntPtr[] SigscanAll(IntPtr module, int moduleSize, string s ++index; } - return ptrs.ToArray(); + return [.. ptrs]; } public static unsafe IntPtr Sigscan(IntPtr module, int moduleSize, string signature) diff --git a/MelonLoader/Pastel/Pastel.cs b/MelonLoader/Pastel/Pastel.cs index 6cb6f1db1..df7bdda5e 100644 --- a/MelonLoader/Pastel/Pastel.cs +++ b/MelonLoader/Pastel/Pastel.cs @@ -49,7 +49,7 @@ private enum ColorPlane : byte }; private static readonly Regex _closeNestedPastelStringRegex1 = new($"({_formatStringEnd.Replace("[", @"\[")})+", RegexOptions.Compiled); - private static readonly Regex _closeNestedPastelStringRegex2 = new($"(?().ToArray())})(?:{string.Format(_formatStringStart.Replace("[", @"\["), $"(?:{_planeFormatModifiers[ColorPlane.Foreground]}|{_planeFormatModifiers[ColorPlane.Background]})")})", RegexOptions.Compiled); + private static readonly Regex _closeNestedPastelStringRegex2 = new($"(?()])})(?:{string.Format(_formatStringStart.Replace("[", @"\["), $"(?:{_planeFormatModifiers[ColorPlane.Foreground]}|{_planeFormatModifiers[ColorPlane.Background]})")})", RegexOptions.Compiled); private static readonly Dictionary _closeNestedPastelStringRegex3 = new() { diff --git a/MelonLoader/Preferences/IO/Watcher.cs b/MelonLoader/Preferences/IO/Watcher.cs index 7f8415ba7..c950a2e80 100644 --- a/MelonLoader/Preferences/IO/Watcher.cs +++ b/MelonLoader/Preferences/IO/Watcher.cs @@ -17,9 +17,7 @@ internal Watcher(File preffile) return; try { - var method = AccessTools.PropertyGetter(typeof(FileSystemWatcher), "Path"); - if (method == null) - throw new NullReferenceException("No Path Property Get Method Found!"); + var method = AccessTools.PropertyGetter(typeof(FileSystemWatcher), "Path") ?? throw new NullReferenceException("No Path Property Get Method Found!"); if (method.IsNotImplemented()) { MelonLogger.Warning("FileSystemWatcher NotImplementedException Detected! Disabling MelonPreferences FileWatcher Functionality..."); diff --git a/MelonLoader/ResolvedMelons.cs b/MelonLoader/ResolvedMelons.cs index 2afa81d00..3712cf832 100644 --- a/MelonLoader/ResolvedMelons.cs +++ b/MelonLoader/ResolvedMelons.cs @@ -1,13 +1,7 @@ namespace MelonLoader; -public sealed class ResolvedMelons // This class only exists because I can't use Tuples +public sealed class ResolvedMelons(MelonBase[] loadedMelons, RottenMelon[] rottenMelons) // This class only exists because I can't use Tuples { - public readonly MelonBase[] loadedMelons; - public readonly RottenMelon[] rottenMelons; - - public ResolvedMelons(MelonBase[] loadedMelons, RottenMelon[] rottenMelons) - { - this.loadedMelons = loadedMelons ?? new MelonBase[0]; - this.rottenMelons = rottenMelons ?? new RottenMelon[0]; - } + public readonly MelonBase[] loadedMelons = loadedMelons ?? []; + public readonly RottenMelon[] rottenMelons = rottenMelons ?? []; } diff --git a/MelonLoader/Resolver/SearchDirectoryManager.cs b/MelonLoader/Resolver/SearchDirectoryManager.cs index e86d36082..0b93de354 100644 --- a/MelonLoader/Resolver/SearchDirectoryManager.cs +++ b/MelonLoader/Resolver/SearchDirectoryManager.cs @@ -18,8 +18,7 @@ internal static class SearchDirectoryManager private static List SearchDirectoryList = []; private static void Sort() - => SearchDirectoryList = - SearchDirectoryList.OrderBy(x => x.Priority).ToList(); + => SearchDirectoryList = [.. SearchDirectoryList.OrderBy(x => x.Priority)]; internal static void Add(string path, int priority = 0) { diff --git a/MelonLoader/RottenMelon.cs b/MelonLoader/RottenMelon.cs index 62753c554..d496e80f4 100644 --- a/MelonLoader/RottenMelon.cs +++ b/MelonLoader/RottenMelon.cs @@ -5,18 +5,10 @@ namespace MelonLoader; /// /// An info class for broken Melons. /// -public sealed class RottenMelon +public sealed class RottenMelon(Type type, string errorMessage, Exception exception = null) { - public readonly MelonAssembly assembly; - public readonly Type type; - public readonly string errorMessage; - public readonly Exception exception; - - public RottenMelon(Type type, string errorMessage, Exception exception = null) - { - assembly = MelonAssembly.LoadMelonAssembly(null, type.Assembly); - this.type = type; - this.errorMessage = errorMessage; - this.exception = exception; - } + public readonly MelonAssembly assembly = MelonAssembly.LoadMelonAssembly(null, type.Assembly); + public readonly Type type = type; + public readonly string errorMessage = errorMessage; + public readonly Exception exception = exception; } diff --git a/MelonLoader/SupportModule.cs b/MelonLoader/SupportModule.cs index 1aaacaf2f..f8931eeec 100644 --- a/MelonLoader/SupportModule.cs +++ b/MelonLoader/SupportModule.cs @@ -92,7 +92,7 @@ private static bool LoadInterface(string ModulePath) return false; } - Interface = (ISupportModuleTo)method.Invoke(null, new object[] { new SupportModule_From() }); + Interface = (ISupportModuleTo)method.Invoke(null, [new SupportModule_From()]); if (Interface == null) { MelonLogger.Error("Failed to Initialize Interface!"); diff --git a/MelonLoader/VerifyLoaderBuildAttribute.cs b/MelonLoader/VerifyLoaderBuildAttribute.cs index 492c22fe2..baaa490ca 100644 --- a/MelonLoader/VerifyLoaderBuildAttribute.cs +++ b/MelonLoader/VerifyLoaderBuildAttribute.cs @@ -3,14 +3,12 @@ namespace MelonLoader; [AttributeUsage(AttributeTargets.Assembly)] -public class VerifyLoaderBuildAttribute : Attribute +public class VerifyLoaderBuildAttribute(string hashcode) : Attribute { /// /// Build HashCode of MelonLoader. /// - public string HashCode { get; internal set; } - - public VerifyLoaderBuildAttribute(string hashcode) { HashCode = hashcode; } + public string HashCode { get; internal set; } = hashcode; public bool IsCompatible(string hashCode) => string.IsNullOrEmpty(HashCode) || string.IsNullOrEmpty(hashCode) || HashCode == hashCode; diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs index 60b2b4bc3..417ec129e 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs @@ -2,15 +2,13 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays; using MelonLoader; using System; +using System.Diagnostics.CodeAnalysis; namespace UnityEngine; -public class Il2CppAssetBundle +[SuppressMessage("Naming", "CA1708:Identifiers should differ by more than case", Justification = "Deprecated members")] +public class Il2CppAssetBundle(IntPtr ptr) { - private readonly IntPtr bundleptr = IntPtr.Zero; - - public Il2CppAssetBundle(IntPtr ptr) { bundleptr = ptr; } - static Il2CppAssetBundle() { get_isStreamedSceneAssetBundleDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::get_isStreamedSceneAssetBundle"); @@ -25,51 +23,59 @@ static Il2CppAssetBundle() UnloadDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::Unload"); } - public bool isStreamedSceneAssetBundle + public bool IsStreamedSceneAssetBundle { get { - return bundleptr == IntPtr.Zero + return ptr == IntPtr.Zero ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero") : get_isStreamedSceneAssetBundleDelegateField == null ? throw new NullReferenceException("The get_isStreamedSceneAssetBundleDelegateField cannot be null.") - : get_isStreamedSceneAssetBundleDelegateField(bundleptr); + : get_isStreamedSceneAssetBundleDelegateField(ptr); } } - public Object mainAsset + [Obsolete("Use IsStreamedSceneAssetBundle (starting with upper-case) instead.", true)] + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] + public bool isStreamedSceneAssetBundle => IsStreamedSceneAssetBundle; + + public Object MainAsset { get { - if (bundleptr == IntPtr.Zero) + if (ptr == IntPtr.Zero) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (returnMainAssetDelegateField == null) throw new NullReferenceException("The returnMainAssetDelegateField cannot be null."); - var intPtr = returnMainAssetDelegateField(bundleptr); + var intPtr = returnMainAssetDelegateField(ptr); return (intPtr != IntPtr.Zero) ? new Object(intPtr) : null; } } + [Obsolete("Use MainAsset (starting with upper-case) instead.", true)] + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] + public Object mainAsset => MainAsset; + public bool Contains(string name) { - return bundleptr == IntPtr.Zero + return ptr == IntPtr.Zero ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero") : string.IsNullOrEmpty(name) ? throw new ArgumentException("The input asset name cannot be null or empty.") : ContainsDelegateField == null ? throw new NullReferenceException("The ContainsDelegateField cannot be null.") - : ContainsDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name)); + : ContainsDelegateField(ptr, IL2CPP.ManagedStringToIl2Cpp(name)); } public Il2CppStringArray AllAssetNames() => GetAllAssetNames(); public Il2CppStringArray GetAllAssetNames() { - if (bundleptr == IntPtr.Zero) + if (ptr == IntPtr.Zero) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (GetAllAssetNamesDelegateField == null) throw new NullReferenceException("The GetAllAssetNamesDelegateField cannot be null."); - var intPtr = GetAllAssetNamesDelegateField(bundleptr); + var intPtr = GetAllAssetNamesDelegateField(ptr); return (intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null; } @@ -77,11 +83,11 @@ public Il2CppStringArray GetAllAssetNames() public Il2CppStringArray GetAllScenePaths() { - if (bundleptr == IntPtr.Zero) + if (ptr == IntPtr.Zero) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (GetAllScenePathsDelegateField == null) throw new NullReferenceException("The GetAllScenePathsDelegateField cannot be null."); - var intPtr = GetAllScenePathsDelegateField(bundleptr); + var intPtr = GetAllScenePathsDelegateField(ptr); return (intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null; } @@ -113,7 +119,7 @@ public Object LoadAsset(string name, Il2CppSystem.Type type) public IntPtr LoadAsset(string name, IntPtr typeptr) { - return bundleptr == IntPtr.Zero + return ptr == IntPtr.Zero ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero") : string.IsNullOrEmpty(name) ? throw new ArgumentException("The input asset name cannot be null or empty.") @@ -121,7 +127,7 @@ public IntPtr LoadAsset(string name, IntPtr typeptr) ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") : LoadAsset_InternalDelegateField == null ? throw new NullReferenceException("The LoadAsset_InternalDelegateField cannot be null.") - : LoadAsset_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + : LoadAsset_InternalDelegateField(ptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public Il2CppAssetBundleRequest LoadAssetAsync(string name) => LoadAssetAsync(name); @@ -144,7 +150,7 @@ public Il2CppAssetBundleRequest LoadAssetAsync(string name, Il2CppSystem.Type ty public IntPtr LoadAssetAsync(string name, IntPtr typeptr) { - return bundleptr == IntPtr.Zero + return ptr == IntPtr.Zero ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero") : string.IsNullOrEmpty(name) ? throw new ArgumentException("The input asset name cannot be null or empty.") @@ -152,7 +158,7 @@ public IntPtr LoadAssetAsync(string name, IntPtr typeptr) ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") : LoadAssetAsync_InternalDelegateField == null ? throw new NullReferenceException("The LoadAssetAsync_InternalDelegateField cannot be null.") - : LoadAssetAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + : LoadAssetAsync_InternalDelegateField(ptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public Il2CppReferenceArray LoadAll() => LoadAllAssets(); @@ -187,7 +193,7 @@ public IntPtr LoadAllAssets(IntPtr typeptr) ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") : LoadAssetWithSubAssets_InternalDelegateField == null ? throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null.") - : LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(string.Empty), typeptr); + : LoadAssetWithSubAssets_InternalDelegateField(ptr, IL2CPP.ManagedStringToIl2Cpp(string.Empty), typeptr); } public Il2CppReferenceArray LoadAssetWithSubAssets(string name) => LoadAssetWithSubAssets(name); @@ -210,7 +216,7 @@ public Il2CppReferenceArray LoadAssetWithSubAssets(string name, Il2CppSy public IntPtr LoadAssetWithSubAssets(string name, IntPtr typeptr) { - return bundleptr == IntPtr.Zero + return ptr == IntPtr.Zero ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero") : string.IsNullOrEmpty(name) ? throw new ArgumentException("The input asset name cannot be null or empty.") @@ -218,7 +224,7 @@ public IntPtr LoadAssetWithSubAssets(string name, IntPtr typeptr) ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") : LoadAssetWithSubAssets_InternalDelegateField == null ? throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null.") - : LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + : LoadAssetWithSubAssets_InternalDelegateField(ptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name) => LoadAssetWithSubAssetsAsync(name); @@ -240,7 +246,7 @@ public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name, Il2CppS public IntPtr LoadAssetWithSubAssetsAsync(string name, IntPtr typeptr) { - return bundleptr == IntPtr.Zero + return ptr == IntPtr.Zero ? throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero") : string.IsNullOrEmpty(name) ? throw new ArgumentException("The input asset name cannot be null or empty.") @@ -248,16 +254,16 @@ public IntPtr LoadAssetWithSubAssetsAsync(string name, IntPtr typeptr) ? throw new NullReferenceException("The input type cannot be IntPtr.Zero") : LoadAssetWithSubAssetsAsync_InternalDelegateField == null ? throw new NullReferenceException("The LoadAssetWithSubAssetsAsync_InternalDelegateField cannot be null.") - : LoadAssetWithSubAssetsAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + : LoadAssetWithSubAssetsAsync_InternalDelegateField(ptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); } public void Unload(bool unloadAllLoadedObjects) { - if (bundleptr == IntPtr.Zero) + if (ptr == IntPtr.Zero) throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); if (UnloadDelegateField == null) throw new NullReferenceException("The UnloadDelegateField cannot be null."); - UnloadDelegateField(bundleptr, unloadAllLoadedObjects); + UnloadDelegateField(ptr, unloadAllLoadedObjects); } private delegate bool get_isStreamedSceneAssetBundleDelegate(IntPtr _this); diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs index 092bf4238..a374406d6 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs @@ -22,14 +22,14 @@ public static Il2CppAssetBundle[] GetAllLoadedAssetBundles() { if (GetAllLoadedAssetBundles_NativeDelegateField == null) throw new System.NullReferenceException("The GetAllLoadedAssetBundles_NativeDelegateField cannot be null."); + var intPtr = GetAllLoadedAssetBundles_NativeDelegateField(); - var refarr = (intPtr != System.IntPtr.Zero) ? new Il2CppReferenceArray(intPtr) : null; - if (refarr == null) - throw new System.NullReferenceException("The refarr cannot be null."); + var refarr = ((intPtr != System.IntPtr.Zero) ? new Il2CppReferenceArray(intPtr) : null) ?? throw new System.NullReferenceException("The refarr cannot be null."); System.Collections.Generic.List bundlelist = []; for (var i = 0; i < refarr.Length; i++) bundlelist.Add(new Il2CppAssetBundle(IL2CPP.Il2CppObjectBaseToPtrNotNull(refarr[i]))); - return bundlelist.ToArray(); + + return [.. bundlelist]; } public static Il2CppAssetBundle LoadFromFile(string path) => LoadFromFile(path, 0u, 0UL); diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs index b5ca2be1e..6cfbbbe67 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs @@ -1,13 +1,13 @@ using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes.Arrays; using System; +using System.Diagnostics.CodeAnalysis; namespace UnityEngine; -public class Il2CppAssetBundleCreateRequest : AsyncOperation +[SuppressMessage("Naming", "CA1708:Identifiers should differ by more than case", Justification = "Deprecated members")] +public class Il2CppAssetBundleCreateRequest(IntPtr ptr) : AsyncOperation(ptr) { - public Il2CppAssetBundleCreateRequest(IntPtr ptr) : base(ptr) { } - static Il2CppAssetBundleCreateRequest() { Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp(); @@ -15,7 +15,7 @@ static Il2CppAssetBundleCreateRequest() get_assetBundleDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundleCreateRequest::get_assetBundle"); } - public Il2CppAssetBundle assetBundle + public Il2CppAssetBundle AssetBundle { [Il2CppInterop.Runtime.Attributes.HideFromIl2Cpp] get @@ -25,14 +25,17 @@ public Il2CppAssetBundle assetBundle } } + [Obsolete("Use AssetBundle (starting with upper-case) instead.", true)] + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] + public Il2CppAssetBundle assetBundle => AssetBundle; + private delegate IntPtr get_assetBundleDelegate(IntPtr _this); private static readonly get_assetBundleDelegate get_assetBundleDelegateField; } -public class Il2CppAssetBundleRequest : AsyncOperation +[SuppressMessage("Naming", "CA1708:Identifiers should differ by more than case", Justification = "Deprecated members")] +public class Il2CppAssetBundleRequest(IntPtr ptr) : AsyncOperation(ptr) { - public Il2CppAssetBundleRequest(IntPtr ptr) : base(ptr) { } - static Il2CppAssetBundleRequest() { Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp(); @@ -41,7 +44,7 @@ static Il2CppAssetBundleRequest() get_allAssetsDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundleRequest::get_allAssets"); } - public Object asset + public Object Asset { get { @@ -50,7 +53,11 @@ public Object asset } } - public Il2CppReferenceArray allAssets + [Obsolete("Use Asset (starting with upper-case) instead.", true)] + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] + public Object asset => Asset; + + public Il2CppReferenceArray AllAssets { get { @@ -59,6 +66,10 @@ public Il2CppReferenceArray allAssets } } + [Obsolete("Use AllAssets (starting with upper-case) instead.", true)] + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] + public Il2CppReferenceArray allAssets => AllAssets; + private delegate IntPtr get_assetDelegate(IntPtr _this); private static readonly get_assetDelegate get_assetDelegateField; From abc56d27bca73116b24f2c861769cedc4fffb918 Mon Sep 17 00:00:00 2001 From: slxdy Date: Thu, 23 Jan 2025 00:31:45 +0100 Subject: [PATCH 17/18] Force all obsolete members to error --- .../Il2CppAssemblyGenerator/Config.cs | 2 +- Dependencies/SupportModules/Il2Cpp/Main.cs | 9 +- MelonLoader/Assertions/LemonAssertMapping.cs | 4 +- .../Harmony/Attributes.cs | 86 +++++++++---------- .../Harmony/Extras/FastAccess.cs | 12 +-- .../Harmony/HarmonyInstance.cs | 2 +- .../Harmony/HarmonyMethod.cs | 24 +++--- .../BackwardsCompatibility/Harmony/Patch.cs | 10 +-- .../Harmony/Priority.cs | 20 ++--- .../Harmony/Tools/AccessTools.cs | 2 +- .../Harmony/Tools/Extensions.cs | 26 +++--- .../Harmony/Tools/SymbolExtensions.cs | 10 +-- .../Melon/AssemblyResolveInfo.cs | 2 +- .../Melon/HarmonyShield.cs | 2 +- .../BackwardsCompatibility/Melon/Imports.cs | 20 ++--- .../BackwardsCompatibility/Melon/Main.cs | 12 +-- .../Melon/MelonConsole.cs | 4 +- .../Melon/MelonLoaderBase.cs | 6 +- .../Melon/MelonModGameAttribute.cs | 8 +- .../Melon/MelonModInfoAttribute.cs | 14 +-- .../Melon/MelonModLogger.cs | 2 +- .../Melon/MelonPluginGameAttribute.cs | 8 +- .../Melon/MelonPluginInfoAttribute.cs | 14 +-- .../Melon/MelonPrefs.cs | 50 +++++------ .../BackwardsCompatibility/Melon/ModPrefs.cs | 22 ++--- .../Melon/MonoLibrary.cs | 2 +- .../Melon/MonoResolveManager.cs | 18 ++-- .../BackwardsCompatibility/Melon/bHaptics.cs | 63 +++++++------- .../TinyJSON/EncodeOptions.cs | 2 +- MelonLoader/InternalUtils/BootstrapInterop.cs | 11 +-- MelonLoader/MelonAuthorColorAttribute.cs | 4 +- MelonLoader/MelonBase.cs | 70 +++++++++------ MelonLoader/MelonColorAttribute.cs | 4 +- MelonLoader/MelonGameAttribute.cs | 8 +- MelonLoader/MelonHandler.cs | 34 ++++---- MelonLoader/MelonLaunchOptions.cs | 58 ++++++------- MelonLoader/MelonLogger.cs | 26 +++--- MelonLoader/MelonMod.cs | 22 +++-- MelonLoader/MelonPlugin.cs | 17 +++- MelonLoader/MelonPreferences_Entry.cs | 6 +- MelonLoader/MelonUtils.cs | 52 +++++------ MelonLoader/Resolver/MelonAssemblyResolver.cs | 31 +++++-- .../Il2CppAssetBundle.cs | 4 +- .../Il2CppAssetBundleRequest.cs | 6 +- 44 files changed, 429 insertions(+), 380 deletions(-) diff --git a/Dependencies/Il2CppAssemblyGenerator/Config.cs b/Dependencies/Il2CppAssemblyGenerator/Config.cs index 1f4d8fc0a..29649f59f 100644 --- a/Dependencies/Il2CppAssemblyGenerator/Config.cs +++ b/Dependencies/Il2CppAssemblyGenerator/Config.cs @@ -35,7 +35,7 @@ public class AssemblyGeneratorConfiguration public string DumperVersion = "0.0.0.0"; public string DumperSCRSVersion = "0.0.0.0"; - [Obsolete("Il2CppAssemblyUnhollower support was discontinued.", true)] + [Obsolete("Il2CppAssemblyUnhollower support was discontinued. This will be removed in a future version.", true)] public bool UseInterop = true; public List OldFiles = []; diff --git a/Dependencies/SupportModules/Il2Cpp/Main.cs b/Dependencies/SupportModules/Il2Cpp/Main.cs index d94f710ac..9ff4935a3 100644 --- a/Dependencies/SupportModules/Il2Cpp/Main.cs +++ b/Dependencies/SupportModules/Il2Cpp/Main.cs @@ -3,6 +3,7 @@ using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.Startup; using MelonLoader.CoreClrUtils; +using MelonLoader.InternalUtils; using MelonLoader.Modules; using MelonLoader.Support.Preferences; using MelonLoader.Utils; @@ -15,8 +16,6 @@ [assembly: MelonLoader.PatchShield] -#pragma warning disable CS0618 // Type or member is obsolete - namespace MelonLoader.Support; internal static class Main @@ -54,7 +53,7 @@ private static ISupportModuleTo Initialize(ISupportModuleFrom interface_from) }).AddLogger(new InteropLogger()) .AddHarmonySupport(); - if (MelonLaunchOptions.Console.CleanUnityLogs) + if (!LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner) ConsoleCleaner(); SceneHandler.Init(); @@ -150,7 +149,7 @@ public unsafe void Apply() var addr = _detourFrom; var addrPtr = (nint)(&addr); - MelonUtils.NativeHookAttachDirect(addrPtr, _targetPtr); + BootstrapInterop.NativeHookAttachDirect(addrPtr, _targetPtr); NativeStackWalk.RegisterHookAddr((ulong)addrPtr, $"Il2CppInterop detour of 0x{addrPtr:X} -> 0x{_targetPtr:X}"); _originalPtr = addr; @@ -164,7 +163,7 @@ public unsafe void Dispose() var addr = _detourFrom; var addrPtr = (nint)(&addr); - MelonUtils.NativeHookDetach(addrPtr, _targetPtr); + BootstrapInterop.NativeHookDetach(addrPtr, _targetPtr); NativeStackWalk.UnregisterHookAddr((ulong)addrPtr); _targetPtr = IntPtr.Zero; diff --git a/MelonLoader/Assertions/LemonAssertMapping.cs b/MelonLoader/Assertions/LemonAssertMapping.cs index 768fd4bf3..7e64bbb0c 100644 --- a/MelonLoader/Assertions/LemonAssertMapping.cs +++ b/MelonLoader/Assertions/LemonAssertMapping.cs @@ -44,12 +44,12 @@ private static bool IsEqual_object(object obj, object obj2) #region Obsolete - [Obsolete("Use RegisterIsNull instead.", true)] + [Obsolete("Use RegisterIsNull instead. This will be removed in a future version.", true)] [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "Reason for deprecation")] public static void Register_IsNull(Func method) => Register(method, ref IsNull); - [Obsolete("Use RegisterIsNull instead.", true)] + [Obsolete("Use RegisterIsNull instead. This will be removed in a future version.", true)] [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "Reason for deprecation")] public static void Register_IsEqual(Func method) => Register(method, ref IsEqual); diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs b/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs index 70b3a82a1..a4d33dd0a 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Attributes.cs @@ -2,7 +2,7 @@ namespace Harmony; -[Obsolete("Harmony.MethodType is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead.")] +[Obsolete("Harmony.MethodType is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead. This will be removed in a future version.", true)] public enum MethodType { Normal = HarmonyLib.MethodType.Normal, @@ -12,14 +12,14 @@ public enum MethodType StaticConstructor = HarmonyLib.MethodType.StaticConstructor } -[Obsolete("Harmony.PropertyMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead.")] +[Obsolete("Harmony.PropertyMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.MethodType instead. This will be removed in a future version.", true)] public enum PropertyMethod { Getter = HarmonyLib.MethodType.Getter, Setter = HarmonyLib.MethodType.Setter } -[Obsolete("Harmony.ArgumentType is Only Here for Compatibility Reasons. Please use HarmonyLib.ArgumentType instead.")] +[Obsolete("Harmony.ArgumentType is Only Here for Compatibility Reasons. Please use HarmonyLib.ArgumentType instead. This will be removed in a future version.", true)] public enum ArgumentType { Normal = HarmonyLib.ArgumentType.Normal, @@ -28,7 +28,7 @@ public enum ArgumentType Pointer = HarmonyLib.ArgumentType.Pointer } -[Obsolete("Harmony.HarmonyPatchType is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchType instead.")] +[Obsolete("Harmony.HarmonyPatchType is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchType instead. This will be removed in a future version. This will be removed in a future version.", true)] public enum HarmonyPatchType { All = HarmonyLib.HarmonyPatchType.All, @@ -37,111 +37,111 @@ public enum HarmonyPatchType Transpiler = HarmonyLib.HarmonyPatchType.Transpiler } -[Obsolete("Harmony.HarmonyAttribute is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAttribute instead.")] +[Obsolete("Harmony.HarmonyAttribute is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAttribute instead. This will be removed in a future version.", true)] public class HarmonyAttribute : HarmonyLib.HarmonyAttribute { } -[Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] +[Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Method, AllowMultiple = true)] public class HarmonyPatch : HarmonyLib.HarmonyPatch { - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch() : base() { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType) : base(declaringType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, Type[] argumentTypes) : base(declaringType, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, string methodName) : base(declaringType, methodName) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, string methodName, params Type[] argumentTypes) : base(declaringType, methodName, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(declaringType, methodName, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, MethodType methodType) : base(declaringType, (HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, MethodType methodType, params Type[] argumentTypes) : base(declaringType, (HarmonyLib.MethodType)methodType, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(declaringType, (HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type declaringType, string propertyName, MethodType methodType) : base(declaringType, propertyName, (HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(string methodName) : base(methodName) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(string methodName, params Type[] argumentTypes) : base(methodName, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations) : base(methodName, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(string propertyName, MethodType methodType) : base(propertyName, (HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(MethodType methodType) : base((HarmonyLib.MethodType)methodType) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(MethodType methodType, params Type[] argumentTypes) : base((HarmonyLib.MethodType)methodType, argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations) : base((HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type[] argumentTypes) : base(argumentTypes) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(Type[] argumentTypes, ArgumentType[] argumentVariations) : base(argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(string propertyName, PropertyMethod type) : base(propertyName, (HarmonyLib.MethodType)type) { } - [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead.")] + [Obsolete("Harmony.HarmonyPatch is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatch instead. This will be removed in a future version.", true)] public HarmonyPatch(string assemblyQualifiedDeclaringType, string methodName, MethodType methodType, Type[] argumentTypes = null, ArgumentType[] argumentVariations = null) : base(assemblyQualifiedDeclaringType, methodName, (HarmonyLib.MethodType)methodType, argumentTypes, Array.ConvertAll(argumentVariations, x => (HarmonyLib.ArgumentType)x)) { } } -[Obsolete("Harmony.HarmonyPatchAll is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchAll instead.")] +[Obsolete("Harmony.HarmonyPatchAll is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPatchAll instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Class)] public class HarmonyPatchAll : HarmonyLib.HarmonyPatchAll { } -[Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead.")] +[Obsolete("Harmony.HarmonyPriority is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPriority instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class HarmonyPriority(int prioritiy) : HarmonyLib.HarmonyPriority(prioritiy) { } -[Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead.")] +[Obsolete("Harmony.HarmonyBefore is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyBefore instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class HarmonyBefore(params string[] before) : HarmonyLib.HarmonyBefore(before) { } -[Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead.")] +[Obsolete("Harmony.HarmonyAfter is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyAfter instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class HarmonyAfter(params string[] after) : HarmonyLib.HarmonyAfter(after) { } -[Obsolete("Harmony.HarmonyPrepare is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrepare instead.")] +[Obsolete("Harmony.HarmonyPrepare is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrepare instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class HarmonyPrepare : HarmonyLib.HarmonyPrepare { } -[Obsolete("Harmony.HarmonyCleanup is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyCleanup instead.")] +[Obsolete("Harmony.HarmonyCleanup is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyCleanup instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class HarmonyCleanup : HarmonyLib.HarmonyCleanup { } -[Obsolete("Harmony.HarmonyTargetMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethod instead.")] +[Obsolete("Harmony.HarmonyTargetMethod is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethod instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class HarmonyTargetMethod : HarmonyLib.HarmonyTargetMethod { } -[Obsolete("Harmony.HarmonyTargetMethods is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethods instead.")] +[Obsolete("Harmony.HarmonyTargetMethods is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTargetMethods instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class HarmonyTargetMethods : HarmonyLib.HarmonyTargetMethods { } -[Obsolete("Harmony.HarmonyPrefix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrefix instead.")] +[Obsolete("Harmony.HarmonyPrefix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPrefix instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class HarmonyPrefix : HarmonyLib.HarmonyPrefix { } -[Obsolete("Harmony.HarmonyPostfix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPostfix instead.")] +[Obsolete("Harmony.HarmonyPostfix is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyPostfix instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class HarmonyPostfix : HarmonyLib.HarmonyPostfix { } -[Obsolete("Harmony.HarmonyTranspiler is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTranspiler instead.")] +[Obsolete("Harmony.HarmonyTranspiler is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyTranspiler instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Method)] public class HarmonyTranspiler : HarmonyLib.HarmonyTranspiler { } -[Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] +[Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public class HarmonyArgument : HarmonyLib.HarmonyArgument { - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead. This will be removed in a future version.", true)] public HarmonyArgument(string originalName) : base(originalName, null) { } - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead. This will be removed in a future version.", true)] public HarmonyArgument(int index) : base(index, null) { } - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead. This will be removed in a future version.", true)] public HarmonyArgument(string originalName, string newName) : base(originalName, newName) { } - [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead.")] + [Obsolete("Harmony.HarmonyArgument is Only Here for Compatibility Reasons. Please use HarmonyLib.HarmonyArgument instead. This will be removed in a future version.", true)] public HarmonyArgument(int index, string name) : base(index, name) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs index 03cfb326c..d1f635b21 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Extras/FastAccess.cs @@ -11,7 +11,7 @@ namespace Harmony; public class FastAccess { - [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true))")] + [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true)). This will be removed in a future version.", true)] public static InstantiationHandler CreateInstantiationHandler(Type type) { var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, [], null) @@ -23,7 +23,7 @@ public static InstantiationHandler CreateInstantiationHandler(Type type) return (InstantiationHandler)dynamicMethod.Generate().CreateDelegate(typeof(InstantiationHandler)); } - [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true))")] + [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetGetMethod(true)). This will be removed in a future version.", true)] public static GetterHandler CreateGetterHandler(PropertyInfo propertyInfo) { var getMethodInfo = propertyInfo.GetGetMethod(true); @@ -35,7 +35,7 @@ public static GetterHandler CreateGetterHandler(PropertyInfo propertyInfo) return (GetterHandler)dynamicGet.Generate().CreateDelegate(typeof(GetterHandler)); } - [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo)")] + [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo). This will be removed in a future version.", true)] public static GetterHandler CreateGetterHandler(FieldInfo fieldInfo) { var dynamicGet = CreateGetDynamicMethod(fieldInfo.DeclaringType); @@ -47,7 +47,7 @@ public static GetterHandler CreateGetterHandler(FieldInfo fieldInfo) } [Obsolete("Use AccessTools.FieldRefAccess(name) for fields and " + - "AccessTools.MethodDelegate>(AccessTools.PropertyGetter(typeof(T), name)) for properties")] + "AccessTools.MethodDelegate>(AccessTools.PropertyGetter(typeof(T), name)) for properties. This will be removed in a future version.", true)] public static GetterHandler CreateFieldGetter(Type type, params string[] names) { foreach (var name in names) @@ -63,7 +63,7 @@ public static GetterHandler CreateFieldGetter(Type type, params string[] names) return null; } - [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetSetMethod(true))")] + [Obsolete("Use AccessTools.MethodDelegate>(PropertyInfo.GetSetMethod(true)). This will be removed in a future version.", true)] public static SetterHandler CreateSetterHandler(PropertyInfo propertyInfo) { var setMethodInfo = propertyInfo.GetSetMethod(true); @@ -76,7 +76,7 @@ public static SetterHandler CreateSetterHandler(PropertyInfo propertyInfo) return (SetterHandler)dynamicSet.Generate().CreateDelegate(typeof(SetterHandler)); } - [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo)")] + [Obsolete("Use AccessTools.FieldRefAccess(fieldInfo). This will be removed in a future version.", true)] public static SetterHandler CreateSetterHandler(FieldInfo fieldInfo) { var dynamicSet = CreateSetDynamicMethod(fieldInfo.DeclaringType); diff --git a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs index 95cabd08e..c0dc2567c 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyInstance.cs @@ -5,7 +5,7 @@ namespace Harmony; -[Obsolete("Harmony.HarmonyInstance is obsolete. Please use HarmonyLib.Harmony instead.")] +[Obsolete("Harmony.HarmonyInstance is obsolete. Please use HarmonyLib.Harmony instead. This will be removed in a future version.", true)] public class HarmonyInstance(string id) : HarmonyLib.Harmony(id) { public static HarmonyInstance Create(string id) diff --git a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs index 979047be2..b55eebcf0 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/HarmonyMethod.cs @@ -5,33 +5,33 @@ namespace Harmony; -[Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] +[Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead. This will be removed in a future version.", true)] public class HarmonyMethod : HarmonyLib.HarmonyMethod { - [Obsolete("Harmony.HarmonyMethod.prioritiy is obsolete. Please use HarmonyLib.HarmonyMethod.priority instead.")] + [Obsolete("Harmony.HarmonyMethod.prioritiy is obsolete. Please use HarmonyLib.HarmonyMethod.priority instead. This will be removed in a future version.", true)] public int prioritiy = -1; - [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] + [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead. This will be removed in a future version.", true)] public HarmonyMethod() : base() { } - [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] + [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead. This will be removed in a future version.", true)] public HarmonyMethod(MethodInfo method) : base(method) { } - [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead.")] + [Obsolete("Harmony.HarmonyMethod is obsolete. Please use HarmonyLib.HarmonyMethod instead. This will be removed in a future version.", true)] public HarmonyMethod(Type type, string name, Type[] parameters = null) : base(type, name, parameters) { } - [Obsolete("Harmony.HarmonyMethod.Merge is obsolete. Please use HarmonyLib.HarmonyMethod.Merge instead.")] + [Obsolete("Harmony.HarmonyMethod.Merge is obsolete. Please use HarmonyLib.HarmonyMethod.Merge instead. This will be removed in a future version.", true)] public static HarmonyMethod Merge(List attributes) => (HarmonyMethod)Merge(Array.ConvertAll(attributes.ToArray(), x => (HarmonyLib.HarmonyMethod)x).ToList()); public override string ToString() => base.ToString(); } -[Obsolete("Harmony.HarmonyMethodExtensions is obsolete. Please use HarmonyLib.HarmonyMethodExtensions instead.")] +[Obsolete("Harmony.HarmonyMethodExtensions is obsolete. Please use HarmonyLib.HarmonyMethodExtensions instead. This will be removed in a future version.", true)] public static class HarmonyMethodExtensions { - [Obsolete("Harmony.HarmonyMethodExtensions.CopyTo is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.CopyTo instead.")] + [Obsolete("Harmony.HarmonyMethodExtensions.CopyTo is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.CopyTo instead. This will be removed in a future version.", true)] public static void CopyTo(this HarmonyMethod from, HarmonyMethod to) => HarmonyLib.HarmonyMethodExtensions.CopyTo(from, to); - [Obsolete("Harmony.HarmonyMethodExtensions.Clone is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Clone instead.")] + [Obsolete("Harmony.HarmonyMethodExtensions.Clone is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Clone instead. This will be removed in a future version.", true)] public static HarmonyMethod Clone(this HarmonyMethod original) => (HarmonyMethod)HarmonyLib.HarmonyMethodExtensions.Clone(original); - [Obsolete("Harmony.HarmonyMethodExtensions.Merge is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Merge instead.")] + [Obsolete("Harmony.HarmonyMethodExtensions.Merge is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.Merge instead. This will be removed in a future version.", true)] public static HarmonyMethod Merge(this HarmonyMethod master, HarmonyMethod detail) => (HarmonyMethod)HarmonyLib.HarmonyMethodExtensions.Merge(master, detail); - [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(Type) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromType instead.")] + [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(Type) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromType instead. This will be removed in a future version.", true)] public static List GetHarmonyMethods(this Type type) => [.. Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromType(type).ToArray(), x => (HarmonyMethod)x)]; - [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(MethodBase) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromMethod instead.")] + [Obsolete("Harmony.HarmonyMethodExtensions.GetHarmonyMethods(MethodBase) is obsolete. Please use HarmonyLib.HarmonyMethodExtensions.GetFromMethod instead. This will be removed in a future version.", true)] public static List GetHarmonyMethods(this MethodBase method) => [.. Array.ConvertAll(HarmonyLib.HarmonyMethodExtensions.GetFromMethod(method).ToArray(), x => (HarmonyMethod)x)]; } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs b/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs index 9c8ef3df6..e1306f3bc 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Patch.cs @@ -4,7 +4,7 @@ namespace Harmony; -[Obsolete("Harmony.PatchInfoSerialization is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization instead.")] +[Obsolete("Harmony.PatchInfoSerialization is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization instead. This will be removed in a future version.", true)] public static class PatchInfoSerialization { private delegate HarmonyLib.PatchInfo HarmonyLib_PatchInfoSerialization_Deserialize_Delegate(byte[] bytes); @@ -15,17 +15,17 @@ private static readonly HarmonyLib_PatchInfoSerialization_Deserialize_Delegate H private static readonly HarmonyLib_PatchInfoSerialization_PriorityComparer_Delegate HarmonyLib_PatchInfoSerialization_PriorityComparer = HarmonyLib.AccessTools.Method("HarmonyLib.PatchInfoSerialization:PriorityComparer").CreateDelegate(); - [Obsolete("Harmony.PatchInfoSerialization.Deserialize is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.Deserialize instead.")] + [Obsolete("Harmony.PatchInfoSerialization.Deserialize is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.Deserialize instead. This will be removed in a future version.", true)] public static PatchInfo Deserialize(byte[] bytes) => (PatchInfo)HarmonyLib_PatchInfoSerialization_Deserialize(bytes); - [Obsolete("Harmony.PatchInfoSerialization.PriorityComparer is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.PriorityComparer instead.")] + [Obsolete("Harmony.PatchInfoSerialization.PriorityComparer is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfoSerialization.PriorityComparer instead. This will be removed in a future version.", true)] public static int PriorityComparer(object obj, int index, int priority, string[] before, string[] after) => HarmonyLib_PatchInfoSerialization_PriorityComparer(obj, index, priority); } -[Obsolete("Harmony.PatchInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfo instead.")] +[Obsolete("Harmony.PatchInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.PatchInfo instead. This will be removed in a future version.", true)] [Serializable] public class PatchInfo : HarmonyLib.PatchInfo { } -[Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead.")] +[Obsolete("Harmony.Patch is Only Here for Compatibility Reasons. Please use HarmonyLib.Patch instead. This will be removed in a future version.", true)] [Serializable] public class Patch(MethodInfo patch, int index, string owner, int priority, string[] before, string[] after) : IComparable { diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs b/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs index 62d95efcd..2bed5757a 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Priority.cs @@ -2,25 +2,25 @@ namespace Harmony; -[Obsolete("Harmony.Priority is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority instead.")] +[Obsolete("Harmony.Priority is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority instead. This will be removed in a future version.", true)] public static class Priority { - [Obsolete("Harmony.Priority.Last is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Last instead.")] + [Obsolete("Harmony.Priority.Last is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Last instead. This will be removed in a future version.", true)] public const int Last = HarmonyLib.Priority.Last; - [Obsolete("Harmony.Priority.VeryLow is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryLow instead.")] + [Obsolete("Harmony.Priority.VeryLow is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryLow instead. This will be removed in a future version.", true)] public const int VeryLow = HarmonyLib.Priority.VeryLow; - [Obsolete("Harmony.Priority.Low is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Low instead.")] + [Obsolete("Harmony.Priority.Low is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Low instead. This will be removed in a future version.", true)] public const int Low = HarmonyLib.Priority.Low; - [Obsolete("Harmony.Priority.LowerThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.LowerThanNormal instead.")] + [Obsolete("Harmony.Priority.LowerThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.LowerThanNormal instead. This will be removed in a future version.", true)] public const int LowerThanNormal = HarmonyLib.Priority.LowerThanNormal; - [Obsolete("Harmony.Priority.Normal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Normal instead.")] + [Obsolete("Harmony.Priority.Normal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.Normal instead. This will be removed in a future version.", true)] public const int Normal = HarmonyLib.Priority.Normal; - [Obsolete("Harmony.Priority.HigherThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.HigherThanNormal instead.")] + [Obsolete("Harmony.Priority.HigherThanNormal is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.HigherThanNormal instead. This will be removed in a future version.", true)] public const int HigherThanNormal = HarmonyLib.Priority.HigherThanNormal; - [Obsolete("Harmony.Priority.High is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.High instead.")] + [Obsolete("Harmony.Priority.High is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.High instead. This will be removed in a future version.", true)] public const int High = HarmonyLib.Priority.High; - [Obsolete("Harmony.Priority.VeryHigh is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryHigh instead.")] + [Obsolete("Harmony.Priority.VeryHigh is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.VeryHigh instead. This will be removed in a future version.", true)] public const int VeryHigh = HarmonyLib.Priority.VeryHigh; - [Obsolete("Harmony.Priority.First is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.First instead.")] + [Obsolete("Harmony.Priority.First is Only Here for Compatibility Reasons. Please use HarmonyLib.Priority.First instead. This will be removed in a future version.", true)] public const int First = HarmonyLib.Priority.First; } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs b/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs index 8cb9ba2c6..fae9c1bf9 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Tools/AccessTools.cs @@ -4,7 +4,7 @@ namespace Harmony; -[Obsolete("Harmony.AccessTools is Only Here for Compatibility Reasons. Please use HarmonyLib.AccessTools instead.")] +[Obsolete("Harmony.AccessTools is Only Here for Compatibility Reasons. Please use HarmonyLib.AccessTools instead. This will be removed in a future version.", true)] public static class AccessTools { public static BindingFlags all = HarmonyLib.AccessTools.all; diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs b/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs index 771f78db4..198a23f8c 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Tools/Extensions.cs @@ -4,34 +4,34 @@ namespace Harmony; -[Obsolete("Harmony.GeneralExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions instead.")] +[Obsolete("Harmony.GeneralExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions instead. This will be removed in a future version.", true)] public static class GeneralExtensions { - [Obsolete("Harmony.GeneralExtensions.Join is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Join instead.")] + [Obsolete("Harmony.GeneralExtensions.Join is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Join instead. This will be removed in a future version.", true)] public static string Join(this IEnumerable enumeration, Func converter = null, string delimiter = ", ") => HarmonyLib.GeneralExtensions.Join(enumeration, converter, delimiter); - [Obsolete("Harmony.GeneralExtensions.Description is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Description instead.")] + [Obsolete("Harmony.GeneralExtensions.Description is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Description instead. This will be removed in a future version.", true)] public static string Description(this Type[] parameters) => HarmonyLib.GeneralExtensions.Description(parameters); - [Obsolete("Harmony.GeneralExtensions.FullDescription is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.FullDescription instead.")] + [Obsolete("Harmony.GeneralExtensions.FullDescription is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.FullDescription instead. This will be removed in a future version.", true)] public static string FullDescription(this MethodBase method) => HarmonyLib.GeneralExtensions.FullDescription(method); - [Obsolete("Harmony.GeneralExtensions.Types is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Types instead.")] + [Obsolete("Harmony.GeneralExtensions.Types is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.Types instead. This will be removed in a future version.", true)] public static Type[] Types(this ParameterInfo[] pinfo) => HarmonyLib.GeneralExtensions.Types(pinfo); - [Obsolete("Harmony.GeneralExtensions.GetValueSafe is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetValueSafe instead.")] + [Obsolete("Harmony.GeneralExtensions.GetValueSafe is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetValueSafe instead. This will be removed in a future version.", true)] public static T GetValueSafe(this Dictionary dictionary, S key) => HarmonyLib.GeneralExtensions.GetValueSafe(dictionary, key); - [Obsolete("Harmony.GeneralExtensions.GetTypedValue is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetTypedValue instead.")] + [Obsolete("Harmony.GeneralExtensions.GetTypedValue is Only Here for Compatibility Reasons. Please use HarmonyLib.GeneralExtensions.GetTypedValue instead. This will be removed in a future version.", true)] public static T GetTypedValue(this Dictionary dictionary, string key) => HarmonyLib.GeneralExtensions.GetTypedValue(dictionary, key); } -[Obsolete("Harmony.CollectionExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions instead.")] +[Obsolete("Harmony.CollectionExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions instead. This will be removed in a future version.", true)] public static class CollectionExtensions { - [Obsolete("Harmony.CollectionExtensions.Do is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Do instead.")] + [Obsolete("Harmony.CollectionExtensions.Do is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Do instead. This will be removed in a future version.", true)] public static void Do(this IEnumerable sequence, Action action) => HarmonyLib.CollectionExtensions.Do(sequence, action); - [Obsolete("Harmony.CollectionExtensions.DoIf is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.DoIf instead.")] + [Obsolete("Harmony.CollectionExtensions.DoIf is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.DoIf instead. This will be removed in a future version.", true)] public static void DoIf(this IEnumerable sequence, Func condition, Action action) => HarmonyLib.CollectionExtensions.DoIf(sequence, condition, action); - [Obsolete("Harmony.CollectionExtensions.Add is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Add instead.")] + [Obsolete("Harmony.CollectionExtensions.Add is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.Add instead. This will be removed in a future version.", true)] public static IEnumerable Add(this IEnumerable sequence, T item) => HarmonyLib.CollectionExtensions.AddItem(sequence, item); - [Obsolete("Harmony.CollectionExtensions.AddRangeToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddRangeToArray instead.")] + [Obsolete("Harmony.CollectionExtensions.AddRangeToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddRangeToArray instead. This will be removed in a future version.", true)] public static T[] AddRangeToArray(this T[] sequence, T[] items) => HarmonyLib.CollectionExtensions.AddRangeToArray(sequence, items); - [Obsolete("Harmony.CollectionExtensions.AddToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddToArray instead.")] + [Obsolete("Harmony.CollectionExtensions.AddToArray is Only Here for Compatibility Reasons. Please use HarmonyLib.CollectionExtensions.AddToArray instead. This will be removed in a future version.", true)] public static T[] AddToArray(this T[] sequence, T item) => HarmonyLib.CollectionExtensions.AddToArray(sequence, item); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs b/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs index 7597f8504..8fe43466c 100644 --- a/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs +++ b/MelonLoader/BackwardsCompatibility/Harmony/Tools/SymbolExtensions.cs @@ -4,15 +4,15 @@ namespace Harmony; -[Obsolete("Harmony.SymbolExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions instead.")] +[Obsolete("Harmony.SymbolExtensions is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions instead. This will be removed in a future version.", true)] public static class SymbolExtensions { - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead. This will be removed in a future version.", true)] public static MethodInfo GetMethodInfo(Expression expression) => HarmonyLib.SymbolExtensions.GetMethodInfo(expression); - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead. This will be removed in a future version.", true)] public static MethodInfo GetMethodInfo(Expression> expression) => GetMethodInfo((LambdaExpression)expression); - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead. This will be removed in a future version.", true)] public static MethodInfo GetMethodInfo(Expression> expression) => GetMethodInfo((LambdaExpression)expression); - [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead.")] + [Obsolete("Harmony.SymbolExtensions.GetMethodInfo is Only Here for Compatibility Reasons. Please use HarmonyLib.SymbolExtensions.GetMethodInfo instead. This will be removed in a future version.", true)] public static MethodInfo GetMethodInfo(LambdaExpression expression) => HarmonyLib.SymbolExtensions.GetMethodInfo(expression); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs b/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs index 610dcb458..7f0446da3 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/AssemblyResolveInfo.cs @@ -2,5 +2,5 @@ namespace MelonLoader.MonoInternals; -[Obsolete("MelonLoader.MonoInternals.AssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.AssemblyResolveInfo instead.")] +[Obsolete("MelonLoader.MonoInternals.AssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.AssemblyResolveInfo instead. This will be removed in a future version.", true)] public class AssemblyResolveInfo : Resolver.AssemblyResolveInfo { } diff --git a/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs b/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs index 4a8986b9a..8b129bd95 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/HarmonyShield.cs @@ -2,6 +2,6 @@ namespace Harmony; -[Obsolete("Harmony.HarmonyShield is Only Here for Compatibility Reasons. Please use MelonLoader.PatchShield instead.")] +[Obsolete("Harmony.HarmonyShield is Only Here for Compatibility Reasons. Please use MelonLoader.PatchShield instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct)] public class HarmonyShield : MelonLoader.PatchShield { } diff --git a/MelonLoader/BackwardsCompatibility/Melon/Imports.cs b/MelonLoader/BackwardsCompatibility/Melon/Imports.cs index 2fd7624f7..11470472d 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/Imports.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/Imports.cs @@ -3,25 +3,25 @@ namespace MelonLoader; -[Obsolete("MelonLoader.Imports is Only Here for Compatibility Reasons.")] +[Obsolete("MelonLoader.Imports is Only Here for Compatibility Reasons. This will be removed in a future version.", true)] public static class Imports { - [Obsolete("MelonLoader.Imports.GetCompanyName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead.")] + [Obsolete("MelonLoader.Imports.GetCompanyName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead. This will be removed in a future version.", true)] public static string GetCompanyName() => InternalUtils.UnityInformationHandler.GameDeveloper; - [Obsolete("MelonLoader.Imports.GetProductName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead.")] + [Obsolete("MelonLoader.Imports.GetProductName is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead. This will be removed in a future version.", true)] public static string GetProductName() => InternalUtils.UnityInformationHandler.GameName; - [Obsolete("MelonLoader.Imports.GetGameDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.GameRootDirectory instead.")] + [Obsolete("MelonLoader.Imports.GetGameDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.GameRootDirectory instead. This will be removed in a future version.", true)] public static string GetGameDirectory() => MelonEnvironment.GameRootDirectory; - [Obsolete("MelonLoader.Imports.GetGameDataDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UnityGameDataDirectory instead.")] + [Obsolete("MelonLoader.Imports.GetGameDataDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UnityGameDataDirectory instead. This will be removed in a future version.", true)] public static string GetGameDataDirectory() => MelonEnvironment.UnityGameDataDirectory; - [Obsolete("MelonLoader.Imports.GetAssemblyDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.MelonManagedDirectory instead.")] + [Obsolete("MelonLoader.Imports.GetAssemblyDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.MelonManagedDirectory instead. This will be removed in a future version.", true)] public static string GetAssemblyDirectory() => MelonEnvironment.MelonManagedDirectory; - [Obsolete("MelonLoader.Imports.IsIl2CppGame is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsGameIl2Cpp instead.")] + [Obsolete("MelonLoader.Imports.IsIl2CppGame is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsGameIl2Cpp instead. This will be removed in a future version.", true)] public static bool IsIl2CppGame() => MelonUtils.IsGameIl2Cpp(); - [Obsolete("MelonLoader.Imports.IsDebugMode is Only Here for Compatibility Reasons. Please use MelonLoader.MelonDebug.IsEnabled instead.")] + [Obsolete("MelonLoader.Imports.IsDebugMode is Only Here for Compatibility Reasons. Please use MelonLoader.MelonDebug.IsEnabled instead. This will be removed in a future version.", true)] public static bool IsDebugMode() => MelonDebug.IsEnabled(); - [Obsolete("MelonLoader.Imports.Hook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookAttach instead.")] + [Obsolete("MelonLoader.Imports.Hook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookAttach instead. This will be removed in a future version.", true)] public static void Hook(IntPtr target, IntPtr detour) => MelonUtils.NativeHookAttach(target, detour); - [Obsolete("MelonLoader.Imports.Unhook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookDetach instead.")] + [Obsolete("MelonLoader.Imports.Unhook is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.NativeHookDetach instead. This will be removed in a future version.", true)] public static void Unhook(IntPtr target, IntPtr detour) => MelonUtils.NativeHookDetach(target, detour); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/Main.cs b/MelonLoader/BackwardsCompatibility/Melon/Main.cs index bdca93ab0..8821c2d03 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/Main.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/Main.cs @@ -4,17 +4,17 @@ namespace MelonLoader; -[Obsolete("MelonLoader.Main is Only Here for Compatibility Reasons.")] +[Obsolete("MelonLoader.Main is Only Here for Compatibility Reasons. This will be removed in a future version.", true)] public static class Main { - [Obsolete("MelonLoader.Main.Mods is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Mods instead.")] + [Obsolete("MelonLoader.Main.Mods is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Mods instead. This will be removed in a future version.", true)] public static List Mods = null; - [Obsolete("MelonLoader.Main.Plugins is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Plugins instead.")] + [Obsolete("MelonLoader.Main.Plugins is Only Here for Compatibility Reasons. Please use MelonLoader.MelonHandler.Plugins instead. This will be removed in a future version.", true)] public static List Plugins = null; - [Obsolete("MelonLoader.Main.IsBoneworks is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsBONEWORKS instead.")] + [Obsolete("MelonLoader.Main.IsBoneworks is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.IsBONEWORKS instead. This will be removed in a future version.", true)] public static bool IsBoneworks = false; - [Obsolete("MelonLoader.Main.GetUnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] + [Obsolete("MelonLoader.Main.GetUnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead. This will be removed in a future version.", true)] public static string GetUnityVersion() => InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); - [Obsolete("MelonLoader.Main.GetUserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead.")] + [Obsolete("MelonLoader.Main.GetUserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead. This will be removed in a future version.", true)] public static string GetUserDataPath() => MelonEnvironment.UserDataDirectory; } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs index fae25b5f7..533712dfc 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonConsole.cs @@ -2,9 +2,9 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonConsole is Only Here for Compatibility Reasons.")] +[Obsolete("MelonLoader.MelonConsole is Only Here for Compatibility Reasons. This will be removed in a future version.", true)] public class MelonConsole { - [Obsolete("MelonLoader.MelonConsole.SetTitle is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.SetConsoleTitle instead.")] + [Obsolete("MelonLoader.MelonConsole.SetTitle is Only Here for Compatibility Reasons. Please use MelonLoader.MelonUtils.SetConsoleTitle instead. This will be removed in a future version.", true)] public static void SetTitle(string title) => MelonUtils.SetConsoleTitle(title); } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs index e446aea96..87a7db2b9 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonLoaderBase.cs @@ -3,11 +3,11 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonLoaderBase is Only Here for Compatibility Reasons.")] +[Obsolete("MelonLoader.MelonLoaderBase is Only Here for Compatibility Reasons. This will be removed in a future version.", true)] public static class MelonLoaderBase { - [Obsolete("MelonLoader.MelonLoaderBase.UserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead.")] + [Obsolete("MelonLoader.MelonLoaderBase.UserDataPath is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MelonEnvironment.UserDataDirectory instead. This will be removed in a future version.", true)] public static string UserDataPath { get => MelonEnvironment.UserDataDirectory; } - [Obsolete("MelonLoader.MelonLoaderBase.UnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] + [Obsolete("MelonLoader.MelonLoaderBase.UnityVersion is Only Here for Compatibility Reasons. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead. This will be removed in a future version.", true)] public static string UnityVersion { get => InternalUtils.UnityInformationHandler.EngineVersion.ToStringWithoutType(); } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs index b012a47f8..8499d1fa8 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonModGameAttribute.cs @@ -2,14 +2,14 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonModGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] +[Obsolete("MelonLoader.MelonModGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public class MelonModGameAttribute : MelonGameAttribute { - [Obsolete("MelonLoader.MelonModGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead.")] + [Obsolete("MelonLoader.MelonModGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead. This will be removed in a future version.", true)] public new string Developer => base.Developer; - [Obsolete("MelonLoader.MelonModGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead.")] + [Obsolete("MelonLoader.MelonModGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead. This will be removed in a future version.", true)] public string GameName => Name; - [Obsolete("MelonLoader.MelonModGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] + [Obsolete("MelonLoader.MelonModGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead. This will be removed in a future version.", true)] public MelonModGameAttribute(string developer = null, string gameName = null) : base(developer, gameName) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs index 8287f0e52..9750c95a8 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonModInfoAttribute.cs @@ -2,20 +2,20 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] +[Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] public class MelonModInfoAttribute : MelonInfoAttribute { - [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead. This will be removed in a future version.", true)] public new Type SystemType => base.SystemType; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead. This will be removed in a future version.", true)] public new string Name => base.Name; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead. This will be removed in a future version.", true)] public new string Version => base.Version; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead. This will be removed in a future version.", true)] public new string Author => base.Author; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead. This will be removed in a future version.", true)] public new string DownloadLink => base.DownloadLink; - [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead. This will be removed in a future version.", true)] public MelonModInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) : base(type, name, version, author, downloadLink) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs index ddf59d1f6..ffb1d9f33 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonModLogger.cs @@ -2,5 +2,5 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonModLogger is Only Here for Compatibility Reasons. Please use MelonLoader.MelonLogger instead.")] +[Obsolete("MelonLoader.MelonModLogger is Only Here for Compatibility Reasons. Please use MelonLoader.MelonLogger instead. This will be removed in a future version.", true)] public class MelonModLogger : MelonLogger { } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs index 5e4658fad..8702ae03c 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginGameAttribute.cs @@ -2,14 +2,14 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonPluginGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] +[Obsolete("MelonLoader.MelonPluginGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public class MelonPluginGameAttribute : MelonGameAttribute { - [Obsolete("MelonLoader.MelonPluginGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead.")] + [Obsolete("MelonLoader.MelonPluginGameAttribute.Developer is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Developer instead. This will be removed in a future version.", true)] public new string Developer => base.Developer; - [Obsolete("MelonLoader.MelonPluginGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead.")] + [Obsolete("MelonLoader.MelonPluginGameAttribute.GameName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame.Name instead. This will be removed in a future version.", true)] public string GameName => Name; - [Obsolete("MelonLoader.MelonPluginGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead.")] + [Obsolete("MelonLoader.MelonPluginGameAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonGame instead. This will be removed in a future version.", true)] public MelonPluginGameAttribute(string developer = null, string gameName = null) : base(developer, gameName) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs index e84c4e5ff..4007b5c3e 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPluginInfoAttribute.cs @@ -2,20 +2,20 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] +[Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead. This will be removed in a future version.", true)] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] public class MelonPluginInfoAttribute : MelonInfoAttribute { - [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.SystemType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.SystemType instead. This will be removed in a future version.", true)] public new Type SystemType => base.SystemType; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Name is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Name instead. This will be removed in a future version.", true)] public new string Name => base.Name; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Version is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Version instead. This will be removed in a future version.", true)] public new string Version => base.Version; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.Author is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.Author instead. This will be removed in a future version.", true)] public new string Author => base.Author; - [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute.DownloadLink is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo.DownloadLink instead. This will be removed in a future version.", true)] public new string DownloadLink => base.DownloadLink; - [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead.")] + [Obsolete("MelonLoader.MelonPluginInfoAttribute is Only Here for Compatibility Reasons. Please use MelonLoader.MelonInfo instead. This will be removed in a future version.", true)] public MelonPluginInfoAttribute(Type type, string name, string version, string author, string downloadLink = null) : base(type, name, version, author, downloadLink) { } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs index e2271ec28..eb1d285dc 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MelonPrefs.cs @@ -3,22 +3,22 @@ namespace MelonLoader; -[Obsolete("MelonLoader.MelonPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] +[Obsolete("MelonLoader.MelonPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead. This will be removed in a future version.", true)] public class MelonPrefs { - [Obsolete("MelonLoader.MelonPrefs.RegisterCategory is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateCategory instead.")] + [Obsolete("MelonLoader.MelonPrefs.RegisterCategory is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateCategory instead. This will be removed in a future version.", true)] public static void RegisterCategory(string name, string displayText) => MelonPreferences.CreateCategory(name, displayText); - [Obsolete("MelonLoader.MelonPrefs.RegisterString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.MelonPrefs.RegisterString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterString(string section, string name, string defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.RegisterBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.MelonPrefs.RegisterBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterBool(string section, string name, bool defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.RegisterInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.MelonPrefs.RegisterInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterInt(string section, string name, int defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.RegisterFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.MelonPrefs.RegisterFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterFloat(string section, string name, float defaultValue, string displayText = null, bool hideFromList = false) => MelonPreferences.CreateEntry(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.MelonPrefs.HasKey is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.HasEntry instead.")] + [Obsolete("MelonLoader.MelonPrefs.HasKey is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.HasEntry instead. This will be removed in a future version.", true)] public static bool HasKey(string section, string name) => MelonPreferences.HasEntry(section, name); - [Obsolete("MelonLoader.MelonPrefs.GetPreferences is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Categories instead.")] + [Obsolete("MelonLoader.MelonPrefs.GetPreferences is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Categories instead. This will be removed in a future version.", true)] public static Dictionary> GetPreferences() { Dictionary> output = []; @@ -46,11 +46,11 @@ public static Dictionary> GetPrefere return output; } - [Obsolete("MelonLoader.MelonPrefs.GetCategoryDisplayName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetCategoryDisplayName instead.")] + [Obsolete("MelonLoader.MelonPrefs.GetCategoryDisplayName is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetCategoryDisplayName instead. This will be removed in a future version.", true)] public static string GetCategoryDisplayName(string key) => MelonPreferences.GetCategory(key)?.DisplayName; - [Obsolete("MelonLoader.MelonPrefs.SaveConfig is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Save instead.")] + [Obsolete("MelonLoader.MelonPrefs.SaveConfig is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.Save instead. This will be removed in a future version.", true)] public static void SaveConfig() => MelonPreferences.Save(); - [Obsolete("MelonLoader.MelonPrefs.GetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryString instead.")] + [Obsolete("MelonLoader.MelonPrefs.GetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryString instead. This will be removed in a future version.", true)] public static string GetString(string section, string name) { var category = MelonPreferences.GetCategory(section); @@ -59,7 +59,7 @@ public static string GetString(string section, string name) var entry = category.GetEntry(name); return entry?.GetValueAsString(); } - [Obsolete("MelonLoader.MelonPrefs.SetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryString instead.")] + [Obsolete("MelonLoader.MelonPrefs.SetString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryString instead. This will be removed in a future version.", true)] public static void SetString(string section, string name, string value) { var category = MelonPreferences.GetCategory(section); @@ -87,19 +87,19 @@ public static void SetString(string section, string name, string value) break; } } - [Obsolete("MelonLoader.MelonPrefs.GetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryBool instead.")] + [Obsolete("MelonLoader.MelonPrefs.GetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryBool instead. This will be removed in a future version.", true)] public static bool GetBool(string section, string name) => MelonPreferences.GetEntryValue(section, name); - [Obsolete("MelonLoader.MelonPrefs.SetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryBool instead.")] + [Obsolete("MelonLoader.MelonPrefs.SetBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryBool instead. This will be removed in a future version.", true)] public static void SetBool(string section, string name, bool value) => MelonPreferences.SetEntryValue(section, name, value); - [Obsolete("MelonLoader.MelonPrefs.GetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryInt instead.")] + [Obsolete("MelonLoader.MelonPrefs.GetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryInt instead. This will be removed in a future version.", true)] public static int GetInt(string section, string name) => MelonPreferences.GetEntryValue(section, name); - [Obsolete("MelonLoader.MelonPrefs.SetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryInt instead.")] + [Obsolete("MelonLoader.MelonPrefs.SetInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryInt instead. This will be removed in a future version.", true)] public static void SetInt(string section, string name, int value) => MelonPreferences.SetEntryValue(section, name, value); - [Obsolete("MelonLoader.MelonPrefs.GetFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryFloat instead.")] + [Obsolete("MelonLoader.MelonPrefs.GetFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.GetEntryFloat instead. This will be removed in a future version.", true)] public static float GetFloat(string section, string name) => MelonPreferences.GetEntryValue(section, name); - [Obsolete("MelonLoader.MelonPrefs.GetEntryFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryFloat instead.")] + [Obsolete("MelonLoader.MelonPrefs.GetEntryFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.SetEntryFloat instead. This will be removed in a future version.", true)] public static void SetFloat(string section, string name, float value) => MelonPreferences.SetEntryValue(section, name, value); - [Obsolete("MelonLoader.MelonPrefs.MelonPreferenceType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead.")] + [Obsolete("MelonLoader.MelonPrefs.MelonPreferenceType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead. This will be removed in a future version.", true)] public enum MelonPreferenceType { STRING, @@ -107,14 +107,14 @@ public enum MelonPreferenceType INT, FLOAT } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] + [Obsolete("MelonLoader.MelonPrefs.MelonPreference is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead. This will be removed in a future version.", true)] public class MelonPreference { - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Value is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValue instead.")] + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Value is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValue instead. This will be removed in a future version.", true)] public string Value { get => GetString(Entry.Category.Identifier, Entry.Identifier); set => SetString(Entry.Category.Identifier, Entry.Identifier, value); } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.ValueEdited is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValueEdited instead.")] + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.ValueEdited is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetValueEdited instead. This will be removed in a future version.", true)] public string ValueEdited { get => GetEditedString(Entry.Category.Identifier, Entry.Identifier); set => SetEditedString(Entry.Category.Identifier, Entry.Identifier, value); } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetReflectedType instead.")] + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.GetReflectedType instead. This will be removed in a future version.", true)] public MelonPreferenceType Type { get @@ -133,9 +133,9 @@ public MelonPreferenceType Type return (MelonPreferenceType)4; } } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Hidden is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.IsHidden instead.")] + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.Hidden is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.IsHidden instead. This will be removed in a future version.", true)] public bool Hidden { get => Entry.IsHidden; } - [Obsolete("MelonLoader.MelonPrefs.MelonPreference.DisplayText is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.DisplayName instead.")] + [Obsolete("MelonLoader.MelonPrefs.MelonPreference.DisplayText is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.DisplayName instead. This will be removed in a future version.", true)] public string DisplayText { get => Entry.DisplayName; } internal MelonPreference(MelonPreferences_Entry entry) => Entry = entry; diff --git a/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs b/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs index b90c76c89..33be8097c 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/ModPrefs.cs @@ -5,10 +5,10 @@ namespace MelonLoader; -[Obsolete("MelonLoader.ModPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] +[Obsolete("MelonLoader.ModPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead. This will be removed in a future version.", true)] public class ModPrefs : MelonPrefs { - [Obsolete("MelonLoader.ModPrefs.GetPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead.")] + [Obsolete("MelonLoader.ModPrefs.GetPrefs is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences instead. This will be removed in a future version.", true)] public static Dictionary> GetPrefs() { Dictionary> output = []; @@ -32,15 +32,15 @@ public static Dictionary> GetPrefs() return output; } - [Obsolete("MelonLoader.ModPrefs.RegisterPrefString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.ModPrefs.RegisterPrefString is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterPrefString(string section, string name, string defaultValue, string displayText = null, bool hideFromList = false) => RegisterString(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.RegisterPrefBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.ModPrefs.RegisterPrefBool is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterPrefBool(string section, string name, bool defaultValue, string displayText = null, bool hideFromList = false) => RegisterBool(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.RegisterPrefInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.ModPrefs.RegisterPrefInt is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterPrefInt(string section, string name, int defaultValue, string displayText = null, bool hideFromList = false) => RegisterInt(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.RegisterPrefFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead.")] + [Obsolete("MelonLoader.ModPrefs.RegisterPrefFloat is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences.CreateEntry instead. This will be removed in a future version.", true)] public static void RegisterPrefFloat(string section, string name, float defaultValue, string displayText = null, bool hideFromList = false) => RegisterFloat(section, name, defaultValue, displayText, hideFromList); - [Obsolete("MelonLoader.ModPrefs.PrefType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead.")] + [Obsolete("MelonLoader.ModPrefs.PrefType is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.TypeEnum instead. This will be removed in a future version.", true)] public enum PrefType { STRING, @@ -48,14 +48,14 @@ public enum PrefType INT, FLOAT } - [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] + [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead. This will be removed in a future version.", true)] public class PrefDesc : MelonPreference { - [Obsolete("MelonLoader.ModPrefs.PrefDesc.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.Type instead.")] + [Obsolete("MelonLoader.ModPrefs.PrefDesc.Type is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry.Type instead. This will be removed in a future version.", true)] public PrefType Type { get => (PrefType)base.Type; } - [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] + [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead. This will be removed in a future version.", true)] public PrefDesc(MelonPreferences_Entry entry) : base(entry) { } - [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead.")] + [Obsolete("MelonLoader.ModPrefs.PrefDesc is Only Here for Compatibility Reasons. Please use MelonLoader.MelonPreferences_Entry instead. This will be removed in a future version.", true)] public PrefDesc(MelonPreference pref) : base(pref) { } } } \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs b/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs index 405e79552..c2104c365 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MonoLibrary.cs @@ -4,7 +4,7 @@ namespace MelonLoader.MonoInternals; -[Obsolete("MelonLoader.MonoInternals.MonoLibrary is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MonoLibrary instead.")] +[Obsolete("MelonLoader.MonoInternals.MonoLibrary is Only Here for Compatibility Reasons. Please use MelonLoader.Utils.MonoLibrary instead. This will be removed in a future version.", true)] public class MonoLibrary : Utils.MonoLibrary { } #endif \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs b/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs index e13a662ee..88a8be442 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/MonoResolveManager.cs @@ -3,36 +3,36 @@ namespace MelonLoader.MonoInternals; -[Obsolete("MelonLoader.MonoInternals.MonoResolveManager is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver instead.")] +[Obsolete("MelonLoader.MonoInternals.MonoResolveManager is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver instead. This will be removed in a future version.", true)] public static class MonoResolveManager { - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.AddSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.AddSearchDirectory instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.AddSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.AddSearchDirectory instead. This will be removed in a future version.", true)] public static void AddSearchDirectory(string path, int priority = 0) => Resolver.SearchDirectoryManager.Add(path, priority); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.RemoveSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.RemoveSearchDirectory instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.RemoveSearchDirectory is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.RemoveSearchDirectory instead. This will be removed in a future version.", true)] public static void RemoveSearchDirectory(string path) => Resolver.SearchDirectoryManager.Remove(path); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoadHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyLoad instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoadHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyLoad instead. This will be removed in a future version.", true)] public delegate void OnAssemblyLoadHandler(Assembly assembly); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead. This will be removed in a future version.", true)] public static event OnAssemblyLoadHandler OnAssemblyLoad; internal static void SafeInvoke_OnAssemblyLoad(Assembly assembly) => OnAssemblyLoad?.Invoke(assembly); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyResolveHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyResolve instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyResolveHandler is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.dOnAssemblyResolve instead. This will be removed in a future version.", true)] public delegate Assembly OnAssemblyResolveHandler(string name, Version version); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.OnAssemblyLoad is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.OnAssemblyLoad instead. This will be removed in a future version.", true)] public static event OnAssemblyResolveHandler OnAssemblyResolve; internal static Assembly SafeInvoke_OnAssemblyResolve(string name, Version version) => OnAssemblyResolve?.Invoke(name, version); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.GetAssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.GetAssemblyResolveInfo instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.GetAssemblyResolveInfo is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.GetAssemblyResolveInfo instead. This will be removed in a future version.", true)] public static AssemblyResolveInfo GetAssemblyResolveInfo(string name) => (AssemblyResolveInfo)Resolver.AssemblyManager.GetInfo(name); - [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.LoadInfoFromAssembly is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.LoadInfoFromAssembly instead.")] + [Obsolete("MelonLoader.MonoInternals.MonoResolveManager.LoadInfoFromAssembly is Only Here for Compatibility Reasons. Please use MelonLoader.Resolver.MelonAssemblyResolver.LoadInfoFromAssembly instead. This will be removed in a future version.", true)] public static void LoadInfoFromAssembly(Assembly assembly) => Resolver.AssemblyManager.LoadInfo(assembly); } diff --git a/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs b/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs index f29ba9dda..c01f132c4 100644 --- a/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs +++ b/MelonLoader/BackwardsCompatibility/Melon/bHaptics.cs @@ -9,24 +9,24 @@ public static class bHaptics { public static bool WasError { get => false; } - [Obsolete("MelonLoader.bHaptics.IsPlaying is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlayingAny instead.")] + [Obsolete("MelonLoader.bHaptics.IsPlaying is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlayingAny instead. This will be removed in a future version.", true)] public static bool IsPlaying() => bHapticsLib.bHapticsManager.IsPlayingAny(); - [Obsolete("MelonLoader.bHaptics.IsPlaying(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlaying instead.")] + [Obsolete("MelonLoader.bHaptics.IsPlaying(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPlaying instead. This will be removed in a future version.", true)] public static bool IsPlaying(string key) => bHapticsLib.bHapticsManager.IsPlaying(key); - [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(DeviceType, bool) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead.")] + [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(DeviceType, bool) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead. This will be removed in a future version.", true)] public static bool IsDeviceConnected(DeviceType type, bool isLeft = true) => IsDeviceConnected(DeviceTypeToPositionType(type, isLeft)); - [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(PositionType) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead.")] + [Obsolete("MelonLoader.bHaptics.IsDeviceConnected(PositionType) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsDeviceConnected instead. This will be removed in a future version.", true)] public static bool IsDeviceConnected(PositionType type) => bHapticsLib.bHapticsManager.IsDeviceConnected(PositionTypeToPositionID(type)); - [Obsolete("MelonLoader.bHaptics.IsFeedbackRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPatternRegistered instead.")] + [Obsolete("MelonLoader.bHaptics.IsFeedbackRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.IsPatternRegistered instead. This will be removed in a future version.", true)] public static bool IsFeedbackRegistered(string key) => bHapticsLib.bHapticsManager.IsPatternRegistered(key); - [Obsolete("MelonLoader.bHaptics.RegisterFeedback is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead.")] + [Obsolete("MelonLoader.bHaptics.RegisterFeedback is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead. This will be removed in a future version.", true)] public static void RegisterFeedback(string key, string tactFileStr) { var proxyArray = new TinyJSON.ProxyArray @@ -36,54 +36,56 @@ public static void RegisterFeedback(string key, string tactFileStr) bHapticsLib.bHapticsManager.RegisterPatternFromJson(key, TinyJSON.Encoder.Encode(proxyArray)); } - [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFile is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead.")] + [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFile is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternFromJson instead. This will be removed in a future version.", true)] public static void RegisterFeedbackFromTactFile(string key, string tactFileStr) => bHapticsLib.bHapticsManager.RegisterPatternFromJson(key, tactFileStr); - [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFileReflected is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternSwappedFromJson instead.")] + [Obsolete("MelonLoader.bHaptics.RegisterFeedbackFromTactFileReflected is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.RegisterPatternSwappedFromJson instead. This will be removed in a future version.", true)] public static void RegisterFeedbackFromTactFileReflected(string key, string tactFileStr) => bHapticsLib.bHapticsManager.RegisterPatternSwappedFromJson(key, tactFileStr); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead. This will be removed in a future version.", true)] public static void SubmitRegistered(string key) => bHapticsLib.bHapticsManager.PlayRegistered(key); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead. This will be removed in a future version.", true)] public static void SubmitRegistered(string key, int startTimeMillis) => bHapticsLib.bHapticsManager.PlayRegistered(key, startTimeMillis); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead. This will be removed in a future version.", true)] public static void SubmitRegistered(string key, string altKey, ScaleOption option) => bHapticsLib.bHapticsManager.PlayRegistered(key, altKey, new bHapticsLib.ScaleOption { Duration = option.Duration, Intensity = option.Intensity }); - [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead.")] + [Obsolete("MelonLoader.bHaptics.SubmitRegistered is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.PlayRegistered instead. This will be removed in a future version.", true)] public static void SubmitRegistered(string key, string altKey, ScaleOption sOption, RotationOption rOption) => bHapticsLib.bHapticsManager.PlayRegistered(key, altKey, new bHapticsLib.ScaleOption { Duration = sOption.Duration, Intensity = sOption.Intensity }, new bHapticsLib.RotationOption { OffsetAngleX = rOption.OffsetX, OffsetY = rOption.OffsetY }); - [Obsolete("MelonLoader.bHaptics.TurnOff is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlayingAll instead.")] + [Obsolete("MelonLoader.bHaptics.TurnOff is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlayingAll instead. This will be removed in a future version.", true)] public static void TurnOff() => bHapticsLib.bHapticsManager.StopPlayingAll(); - [Obsolete("MelonLoader.bHaptics.TurnOff(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlaying instead.")] + [Obsolete("MelonLoader.bHaptics.TurnOff(string) is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.StopPlaying instead. This will be removed in a future version.", true)] public static void TurnOff(string key) => bHapticsLib.bHapticsManager.StopPlaying(key); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead. This will be removed in a future version.", true)] public static void Submit(string key, DeviceType type, bool isLeft, byte[] bytes, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), bytes, durationMillis); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead. This will be removed in a future version.", true)] public static void Submit(string key, PositionType position, byte[] bytes, int durationMillis) => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), bytes); + [Obsolete] private static readonly Converter DotPointConverter = new((x) => new bHapticsLib.DotPoint { Index = x.Index, Intensity = x.Intensity }); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead. This will be removed in a future version.", true)] public static void Submit(string key, DeviceType type, bool isLeft, List points, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), points, durationMillis); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead. This will be removed in a future version.", true)] public static void Submit(string key, PositionType position, List points, int durationMillis) => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), points.ConvertAll(DotPointConverter)); + [Obsolete] private static readonly Converter PathPointConverter = new((x) => new bHapticsLib.PathPoint { @@ -92,20 +94,20 @@ public static void Submit(string key, PositionType position, List poin Intensity = x.Intensity, MotorCount = x.MotorCount }); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead. This will be removed in a future version.", true)] public static void Submit(string key, DeviceType type, bool isLeft, List points, int durationMillis) => Submit(key, DeviceTypeToPositionType(type, isLeft), points, durationMillis); - [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead.")] + [Obsolete("MelonLoader.bHaptics.Submit is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.Play instead. This will be removed in a future version.", true)] public static void Submit(string key, PositionType position, List points, int durationMillis) => bHapticsLib.bHapticsManager.Play(key, durationMillis, PositionTypeToPositionID(position), (bHapticsLib.DotPoint[])null, points.ConvertAll(PathPointConverter)); - [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead.")] + [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead. This will be removed in a future version.", true)] public static FeedbackStatus GetCurrentFeedbackStatus(DeviceType type, bool isLeft = true) => GetCurrentFeedbackStatus(DeviceTypeToPositionType(type, isLeft)); - [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead.")] + [Obsolete("MelonLoader.bHaptics.GetCurrentFeedbackStatus is Only Here for Compatibility Reasons. Please use bHapticsLib.bHapticsManager.GetDeviceStatus instead. This will be removed in a future version.", true)] public static FeedbackStatus GetCurrentFeedbackStatus(PositionType pos) => new() { values = bHapticsLib.bHapticsManager.GetDeviceStatus(PositionTypeToPositionID(pos)) }; - [Obsolete("MelonLoader.bHaptics.DeviceTypeToPositionType is Only Here for Compatibility Reasons.")] + [Obsolete("MelonLoader.bHaptics.DeviceTypeToPositionType is Only Here for Compatibility Reasons. This will be removed in a future version.", true)] public static PositionType DeviceTypeToPositionType(DeviceType pos, bool isLeft = true) => pos switch { @@ -117,7 +119,7 @@ public static PositionType DeviceTypeToPositionType(DeviceType pos, bool isLeft _ => PositionType.Head }; - [Obsolete("MelonLoader.bHaptics.DeviceType is Only Here for Compatibility Reasons.")] + [Obsolete("MelonLoader.bHaptics.DeviceType is Only Here for Compatibility Reasons. This will be removed in a future version.", true)] public enum DeviceType { None = 0, @@ -128,7 +130,7 @@ public enum DeviceType Tactosy_feet = 5 } - [Obsolete("MelonLoader.bHaptics.PositionType is Only Here for Compatibility Reasons. Please use bHapticsLib.PositionID instead.")] + [Obsolete("MelonLoader.bHaptics.PositionType is Only Here for Compatibility Reasons. Please use bHapticsLib.PositionID instead. This will be removed in a future version.", true)] public enum PositionType { All = 0, @@ -153,7 +155,7 @@ public enum PositionType Custom4 = 254 } - [Obsolete("MelonLoader.bHaptics.RotationOption is Only Here for Compatibility Reasons. Please use bHapticsLib.RotationOption instead.")] + [Obsolete("MelonLoader.bHaptics.RotationOption is Only Here for Compatibility Reasons. Please use bHapticsLib.RotationOption instead. This will be removed in a future version.", true)] public class RotationOption { public float OffsetX, OffsetY; @@ -168,7 +170,7 @@ public override string ToString() => "RotationOption { OffsetX=" + OffsetX.ToStr ", OffsetY=" + OffsetY.ToString() + " }"; } - [Obsolete("MelonLoader.bHaptics.ScaleOption is Only Here for Compatibility Reasons. Please use bHapticsLib.ScaleOption instead.")] + [Obsolete("MelonLoader.bHaptics.ScaleOption is Only Here for Compatibility Reasons. Please use bHapticsLib.ScaleOption instead. This will be removed in a future version.", true)] public class ScaleOption { public float Intensity, Duration; @@ -183,7 +185,7 @@ public override string ToString() => "ScaleOption { Intensity=" + Intensity.ToSt ", Duration=" + Duration.ToString() + " }"; } - [Obsolete("MelonLoader.bHaptics.DotPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.DotPoint instead.")] + [Obsolete("MelonLoader.bHaptics.DotPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.DotPoint instead. This will be removed in a future version.", true)] public class DotPoint { public int Index, Intensity; @@ -200,7 +202,7 @@ public override string ToString() => "DotPoint { Index=" + Index.ToString() + ", Intensity=" + Intensity.ToString() + " }"; } - [Obsolete("MelonLoader.bHaptics.PathPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.PathPoint instead.")] + [Obsolete("MelonLoader.bHaptics.PathPoint is Only Here for Compatibility Reasons. Please use bHapticsLib.PathPoint instead. This will be removed in a future version.", true)] [StructLayout(LayoutKind.Sequential)] public struct PathPoint { @@ -222,7 +224,7 @@ public override string ToString() => "PathPoint { X=" + X.ToString() + ", Intensity=" + Intensity.ToString() + " }"; } - [Obsolete("MelonLoader.bHaptics.FeedbackStatus is Only Here for Compatibility Reasons.")] + [Obsolete("MelonLoader.bHaptics.FeedbackStatus is Only Here for Compatibility Reasons. This will be removed in a future version.", true)] [StructLayout(LayoutKind.Sequential)] public struct FeedbackStatus { @@ -230,6 +232,7 @@ public struct FeedbackStatus public int[] values; }; + [Obsolete] private static bHapticsLib.PositionID PositionTypeToPositionID(PositionType pos) => pos switch { diff --git a/MelonLoader/BackwardsCompatibility/TinyJSON/EncodeOptions.cs b/MelonLoader/BackwardsCompatibility/TinyJSON/EncodeOptions.cs index 447f85e48..8aedc138e 100644 --- a/MelonLoader/BackwardsCompatibility/TinyJSON/EncodeOptions.cs +++ b/MelonLoader/BackwardsCompatibility/TinyJSON/EncodeOptions.cs @@ -12,6 +12,6 @@ public enum EncodeOptions IncludePublicProperties = 4, EnforceHierarchyOrder = 8, - [Obsolete("Use EncodeOptions.EnforceHierarchyOrder instead.")] + [Obsolete("Use EncodeOptions.EnforceHierarchyOrder instead. This will be removed in a future version.", true)] EnforceHeirarchyOrder = EnforceHierarchyOrder } diff --git a/MelonLoader/InternalUtils/BootstrapInterop.cs b/MelonLoader/InternalUtils/BootstrapInterop.cs index 6e67079aa..7a74a4b18 100644 --- a/MelonLoader/InternalUtils/BootstrapInterop.cs +++ b/MelonLoader/InternalUtils/BootstrapInterop.cs @@ -14,14 +14,9 @@ internal static unsafe class BootstrapInterop internal static void SetDefaultConsoleTitleWithGameName(string gameName, string gameVersion = null) { - if (LoaderConfig.Current.Console.DontSetTitle || !Library.IsConsoleOpen()) - return; - var versionStr = $"{Core.GetVersionString()} - {gameName} {gameVersion ?? ""}"; - // Setting the title might not work on .net 2.0. In WTTG 2 it's present in mscorlib, but the resolver can't find it for whatever reason. - // Using reflection to avoid resolver errors - HarmonyLib.AccessTools.Property(typeof(Console), "Title")?.SetValue(null, versionStr, null); + MelonUtils.SetConsoleTitle(versionStr); } #if WINDOWS @@ -40,12 +35,12 @@ internal static void SetDefaultConsoleTitleWithGameName(string gameName, string public static void EnableCloseButton(IntPtr mainWindow) { - EnableMenuItem(GetSystemMenu(mainWindow, 0), SC_CLOSE, MF_BYCOMMAND | MF_ENABLED); + _ = EnableMenuItem(GetSystemMenu(mainWindow, 0), SC_CLOSE, MF_BYCOMMAND | MF_ENABLED); } public static void DisableCloseButton(IntPtr mainWindow) { - EnableMenuItem(GetSystemMenu(mainWindow, 0), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); + _ = EnableMenuItem(GetSystemMenu(mainWindow, 0), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); } #endif diff --git a/MelonLoader/MelonAuthorColorAttribute.cs b/MelonLoader/MelonAuthorColorAttribute.cs index 2d06f1604..2ac72894c 100644 --- a/MelonLoader/MelonAuthorColorAttribute.cs +++ b/MelonLoader/MelonAuthorColorAttribute.cs @@ -10,7 +10,7 @@ public class MelonAuthorColorAttribute : Attribute /// /// Color of the Author Log. /// - [Obsolete("Color is obsolete. Use DrawingColor for full Color support.")] + [Obsolete("Color is obsolete. Use DrawingColor for full Color support. This will be removed in a future version.", true)] public ConsoleColor Color { get => LoggerUtils.DrawingColorToConsoleColor(DrawingColor); @@ -25,7 +25,7 @@ public ConsoleColor Color public MelonAuthorColorAttribute() => DrawingColor = MelonLogger.DefaultTextColor; - [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead.")] + [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead. This will be removed in a future version.", true)] public MelonAuthorColorAttribute(ConsoleColor color) => Color = (color == ConsoleColor.Black) ? LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultMelonColor) : color; diff --git a/MelonLoader/MelonBase.cs b/MelonLoader/MelonBase.cs index b6b7ca11e..9b9f3f5b1 100644 --- a/MelonLoader/MelonBase.cs +++ b/MelonLoader/MelonBase.cs @@ -10,7 +10,6 @@ using System.IO; using System.Linq; using System.Reflection; -#pragma warning disable 0618 namespace MelonLoader; @@ -76,16 +75,16 @@ public static void RegisterSorted(IEnumerable melons) where T : MelonBase private static void SortMelons(ref List melons) where T : MelonBase { DependencyGraph.TopologicalSort(melons); - melons = melons.OrderBy(x => x.Priority).ToList(); + melons = [.. melons.OrderBy(x => x.Priority)]; } #endregion #region Instance - private MelonGameAttribute[] _games = new MelonGameAttribute[0]; - private MelonProcessAttribute[] _processes = new MelonProcessAttribute[0]; - private MelonGameVersionAttribute[] _gameVersions = new MelonGameVersionAttribute[0]; + private MelonGameAttribute[] _games = []; + private MelonProcessAttribute[] _processes = []; + private MelonGameVersionAttribute[] _gameVersions = []; public readonly MelonEvent OnRegister = new(); public readonly MelonEvent OnUnregister = new(); @@ -126,7 +125,7 @@ private static void SortMelons(ref List melons) where T : MelonBase public MelonProcessAttribute[] SupportedProcesses { get => _processes; - internal set => _processes = (value == null || value.Any(x => x.Universal)) ? new MelonProcessAttribute[0] : value; + internal set => _processes = (value == null || value.Any(x => x.Universal)) ? [] : value; } /// @@ -135,7 +134,7 @@ public MelonProcessAttribute[] SupportedProcesses public MelonGameAttribute[] Games { get => _games; - internal set => _games = (value == null || value.Any(x => x.Universal)) ? new MelonGameAttribute[0] : value; + internal set => _games = (value == null || value.Any(x => x.Universal)) ? [] : value; } /// @@ -144,7 +143,7 @@ public MelonGameAttribute[] Games public MelonGameVersionAttribute[] SupportedGameVersions { get => _gameVersions; - internal set => _gameVersions = (value == null || value.Any(x => x.Universal)) ? new MelonGameVersionAttribute[0] : value; + internal set => _gameVersions = (value == null || value.Any(x => x.Universal)) ? [] : value; } /// @@ -310,12 +309,12 @@ public Incompatibility[] FindIncompatiblities(MelonGameAttribute game, string pr result.Add(Incompatibility.MLBuild); } - return result.ToArray(); + return [.. result]; } public Incompatibility[] FindIncompatiblitiesFromContext() { - return FindIncompatiblities(MelonUtils.CurrentGameAttribute, Process.GetCurrentProcess().ProcessName, MelonUtils.GameVersion, BuildInfo.VersionNumber, MelonUtils.HashCode, MelonUtils.CurrentPlatform, MelonUtils.CurrentDomain); + return FindIncompatiblities(MelonUtils.CurrentGameAttribute, Process.GetCurrentProcess().ProcessName, UnityInformationHandler.GameVersion, BuildInfo.VersionNumber, MelonUtils.HashCode, MelonUtils.CurrentPlatform, MelonUtils.CurrentDomain); } public static void PrintIncompatibilities(Incompatibility[] incompatibilities, MelonBase melon) @@ -389,7 +388,7 @@ public bool Register() if (FindMelon(Info.Name, Info.Author) != null) { - MelonLogger.Warning($"Failed to register {MelonTypeName} '{Location}': A Melon with the same Name and Author is already registered!"); + MelonLogger.Warning($"Failed to register {MelonTypeName} '{MelonAssembly.Location}': A Melon with the same Name and Author is already registered!"); return false; } @@ -403,7 +402,7 @@ public bool Register() OnMelonInitializing.Invoke(this); LoggerInstance ??= new MelonLogger.Instance(string.IsNullOrEmpty(ID) ? Info.Name : $"{ID}:{Info.Name}", ConsoleColor); - HarmonyInstance ??= new HarmonyLib.Harmony($"{Assembly.FullName}:{Info.Name}"); + HarmonyInstance ??= new HarmonyLib.Harmony($"{MelonAssembly.Assembly.FullName}:{Info.Name}"); Registered = true; // this has to be true before the melon can subscribe to any events RegisterCallbacks(); @@ -414,7 +413,7 @@ public bool Register() } catch (Exception ex) { - MelonLogger.Error($"Failed to register {MelonTypeName} '{Location}': Melon failed to initialize!"); + MelonLogger.Error($"Failed to register {MelonTypeName} '{MelonAssembly.Location}': Melon failed to initialize!"); MelonLogger.Error(ex.ToString()); Registered = false; return false; @@ -475,16 +474,34 @@ private protected virtual void RegisterCallbacks() MelonEvents.OnLateUpdate.Subscribe(OnLateUpdate, Priority); MelonEvents.OnGUI.Subscribe(OnGUI, Priority); MelonEvents.OnFixedUpdate.Subscribe(OnFixedUpdate, Priority); - MelonEvents.OnApplicationLateStart.Subscribe(OnApplicationLateStart, Priority); MelonPreferences.OnPreferencesLoaded.Subscribe(PrefsLoaded, Priority); MelonPreferences.OnPreferencesSaved.Subscribe(PrefsSaved, Priority); + +#pragma warning disable CS0618 // Type or member is obsolete + RegisterObsoleteCallbacks(); +#pragma warning restore CS0618 // Type or member is obsolete + } + + [Obsolete("Used to make obsolete callbacks still function.")] + private void RegisterObsoleteCallbacks() + { + MelonEvents.OnApplicationLateStart.Subscribe(OnApplicationLateStart, Priority); } private void PrefsSaved(string path) { OnPreferencesSaved(path); OnPreferencesSaved(); + +#pragma warning disable CS0618 // Type or member is obsolete + PrefsSavedObsolete(); +#pragma warning restore CS0618 // Type or member is obsolete + } + + [Obsolete("Used to make the obsolete callback still function.")] + private void PrefsSavedObsolete() + { OnModSettingsApplied(); } @@ -525,7 +542,7 @@ internal void UnregisterInstance(string reason, bool silent) } catch (Exception ex) { - MelonLogger.Error($"Failed to properly unregister {MelonTypeName} '{Location}': Melon failed to deinitialize!"); + MelonLogger.Error($"Failed to properly unregister {MelonTypeName} '{MelonAssembly.Location}': Melon failed to deinitialize!"); MelonLogger.Error(ex.ToString()); } @@ -577,7 +594,7 @@ public static void ExecuteList(LemonAction func, List melons, bool unre { var failedMelons = unregisterOnFail ? new List() : null; - LemonEnumerator enumerator = new(melons.ToArray()); + LemonEnumerator enumerator = new([.. melons]); while (enumerator.MoveNext()) { var melon = enumerator.Current; @@ -605,7 +622,7 @@ public static void ExecuteList(LemonAction func, List melons, bool unre public static void SendMessageAll(string name, params object[] arguments) { - LemonEnumerator enumerator = new(_registeredMelons.ToArray()); + LemonEnumerator enumerator = new([.. _registeredMelons]); while (enumerator.MoveNext()) { var melon = enumerator.Current; @@ -633,18 +650,19 @@ public object SendMessage(string name, params object[] arguments) #region Obsolete Members + [Obsolete] private Harmony.HarmonyInstance _OldHarmonyInstance; - [Obsolete("Please use either the OnLateInitializeMelon callback, or the 'MelonEvents::OnApplicationLateStart' event instead.")] + [Obsolete("Please use either the OnLateInitializeMelon callback, or the 'MelonEvents::OnApplicationLateStart' event instead. This will be removed in a future version.", true)] public virtual void OnApplicationLateStart() { } - [Obsolete("For mods, use OnInitializeMelon instead. For plugins, use OnPreModsLoaded instead.")] + [Obsolete("For mods, use OnInitializeMelon instead. For plugins, use OnPreModsLoaded instead. This will be removed in a future version.", true)] public virtual void OnApplicationStart() { } - [Obsolete("Please use OnPreferencesSaved instead.")] + [Obsolete("Please use OnPreferencesSaved instead. This will be removed in a future version.", true)] public virtual void OnModSettingsApplied() { } - [Obsolete("Please use HarmonyInstance instead.")] + [Obsolete("Please use HarmonyInstance instead. This will be removed in a future version.", true)] #pragma warning disable IDE1006 // Naming Styles public Harmony.HarmonyInstance harmonyInstance { @@ -656,7 +674,7 @@ public Harmony.HarmonyInstance harmonyInstance } #pragma warning restore IDE1006 // Naming Styles - [Obsolete("Please use HarmonyInstance instead.")] + [Obsolete("Please use HarmonyInstance instead. This will be removed in a future version.", true)] public Harmony.HarmonyInstance Harmony { get @@ -666,16 +684,16 @@ public Harmony.HarmonyInstance Harmony } } - [Obsolete("Please use MelonAssembly.Assembly instead.")] + [Obsolete("Please use MelonAssembly.Assembly instead. This will be removed in a future version.", true)] public Assembly Assembly => MelonAssembly.Assembly; - [Obsolete("Please use MelonAssembly.HarmonyDontPatchAll instead.")] + [Obsolete("Please use MelonAssembly.HarmonyDontPatchAll instead. This will be removed in a future version.", true)] public bool HarmonyDontPatchAll => MelonAssembly.HarmonyDontPatchAll; - [Obsolete("Please use MelonAssembly.Hash instead.")] + [Obsolete("Please use MelonAssembly.Hash instead. This will be removed in a future version.", true)] public string Hash => MelonAssembly.Hash; - [Obsolete("Please use MelonAssembly.Location instead.")] + [Obsolete("Please use MelonAssembly.Location instead. This will be removed in a future version.", true)] public string Location => MelonAssembly.Location; #endregion diff --git a/MelonLoader/MelonColorAttribute.cs b/MelonLoader/MelonColorAttribute.cs index ae7cb8276..4468bc771 100644 --- a/MelonLoader/MelonColorAttribute.cs +++ b/MelonLoader/MelonColorAttribute.cs @@ -10,7 +10,7 @@ public class MelonColorAttribute : Attribute /// /// Color of the Melon. /// - [Obsolete("Color is obsolete. Use DrawingColor for full Color support.")] + [Obsolete("Color is obsolete. Use DrawingColor for full Color support. This will be removed in a future version.", true)] public ConsoleColor Color { get => LoggerUtils.DrawingColorToConsoleColor(DrawingColor); @@ -25,7 +25,7 @@ public ConsoleColor Color public MelonColorAttribute() => DrawingColor = MelonLogger.DefaultTextColor; - [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead.")] + [Obsolete("ConsoleColor is obsolete, use the (int, int, int, int) constructor instead. This will be removed in a future version.", true)] public MelonColorAttribute(ConsoleColor color) => Color = (color == ConsoleColor.Black) ? LoggerUtils.DrawingColorToConsoleColor(MelonLogger.DefaultMelonColor) : color; diff --git a/MelonLoader/MelonGameAttribute.cs b/MelonLoader/MelonGameAttribute.cs index 7e65eb095..79218af55 100644 --- a/MelonLoader/MelonGameAttribute.cs +++ b/MelonLoader/MelonGameAttribute.cs @@ -36,12 +36,12 @@ public class MelonGameAttribute(string developer = null, string name = null) : A /// public bool IsCompatibleBecauseUniversal(MelonGameAttribute att) => (att == null) || Universal || att.Universal; - [Obsolete("IsCompatible(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + [Obsolete("IsCompatible(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead. This will be removed in a future version.", true)] public bool IsCompatible(MelonModGameAttribute att) => (att == null) || IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.GameName.Equals(Name)); - [Obsolete("IsCompatible(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + [Obsolete("IsCompatible(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead. This will be removed in a future version.", true)] public bool IsCompatible(MelonPluginGameAttribute att) => (att == null) || IsCompatibleBecauseUniversal(att) || (att.Developer.Equals(Developer) && att.GameName.Equals(Name)); - [Obsolete("IsCompatibleBecauseUniversal(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + [Obsolete("IsCompatibleBecauseUniversal(MelonModGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead. This will be removed in a future version.", true)] public bool IsCompatibleBecauseUniversal(MelonModGameAttribute att) => (att == null) || Universal || string.IsNullOrEmpty(att.Developer) || string.IsNullOrEmpty(att.GameName); - [Obsolete("IsCompatibleBecauseUniversal(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead.")] + [Obsolete("IsCompatibleBecauseUniversal(MelonPluginGameAttribute) is obsolete. Please use IsCompatible(MelonGameAttribute) instead. This will be removed in a future version.", true)] public bool IsCompatibleBecauseUniversal(MelonPluginGameAttribute att) => (att == null) || Universal || string.IsNullOrEmpty(att.Developer) || string.IsNullOrEmpty(att.GameName); } \ No newline at end of file diff --git a/MelonLoader/MelonHandler.cs b/MelonLoader/MelonHandler.cs index 0aa72e8ab..4ae90da18 100644 --- a/MelonLoader/MelonHandler.cs +++ b/MelonLoader/MelonHandler.cs @@ -13,13 +13,13 @@ public static class MelonHandler /// /// Directory of Plugins. /// - [Obsolete("Use MelonEnvironment.PluginsDirectory instead")] + [Obsolete("Use MelonEnvironment.PluginsDirectory instead. This will be removed in a future version.", true)] public static string PluginsDirectory => MelonEnvironment.PluginsDirectory; /// /// Directory of Mods. /// - [Obsolete("Use MelonEnvironment.ModsDirectory instead")] + [Obsolete("Use MelonEnvironment.ModsDirectory instead. This will be removed in a future version.", true)] public static string ModsDirectory => MelonEnvironment.ModsDirectory; internal static void Setup() @@ -44,44 +44,44 @@ public static void LoadMelonsFromDirectory(string path) /// /// List of Plugins. /// - [Obsolete("Use 'MelonPlugin.RegisteredMelons' instead.")] - public static List Plugins => MelonTypeBase.RegisteredMelons.ToList(); + [Obsolete("Use 'MelonPlugin.RegisteredMelons' instead. This will be removed in a future version.", true)] + public static List Plugins => [.. MelonTypeBase.RegisteredMelons]; /// /// List of Mods. /// - [Obsolete("Use 'MelonMod.RegisteredMelons' instead.")] - public static List Mods => MelonTypeBase.RegisteredMelons.ToList(); + [Obsolete("Use 'MelonMod.RegisteredMelons' instead. This will be removed in a future version.", true)] + public static List Mods => [.. MelonTypeBase.RegisteredMelons]; - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead. This will be removed in a future version.", true)] public static void LoadFromFile(string filelocation, bool isPlugin) => LoadFromFile(filelocation); - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead. This will be removed in a future version.", true)] public static void LoadFromByteArray(byte[] filedata, string filelocation) => LoadFromByteArray(filedata, filepath: filelocation); - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead. This will be removed in a future version.", true)] public static void LoadFromByteArray(byte[] filedata, string filelocation, bool isPlugin) => LoadFromByteArray(filedata, filepath: filelocation); - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead. This will be removed in a future version.", true)] public static void LoadFromAssembly(Assembly asm, string filelocation, bool isPlugin) => LoadFromAssembly(asm, filelocation); - [Obsolete("Use 'MelonBase.Hash' instead.")] + [Obsolete("Use 'MelonBase.Hash' instead. This will be removed in a future version.", true)] public static string GetMelonHash(MelonBase melonBase) => melonBase.Hash; - [Obsolete("Use 'MelonBase.RegisteredMelons.Exists(1)' instead.")] + [Obsolete("Use 'MelonBase.RegisteredMelons.Exists(1)' instead. This will be removed in a future version.", true)] public static bool IsMelonAlreadyLoaded(string name) => MelonBase._registeredMelons.Exists(x => x.Info.Name == name); - [Obsolete("Use 'MelonPlugin.RegisteredMelons.Exists(1)' instead.")] + [Obsolete("Use 'MelonPlugin.RegisteredMelons.Exists(1)' instead. This will be removed in a future version.", true)] public static bool IsPluginAlreadyLoaded(string name) => MelonTypeBase._registeredMelons.Exists(x => x.Info.Name == name); - [Obsolete("Use 'MelonMod.RegisteredMelons.Exists(1)' instead.")] + [Obsolete("Use 'MelonMod.RegisteredMelons.Exists(1)' instead. This will be removed in a future version.", true)] public static bool IsModAlreadyLoaded(string name) => MelonTypeBase._registeredMelons.Exists(x => x.Info.Name == name); - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead. This will be removed in a future version.", true)] public static void LoadFromFile(string filepath, string symbolspath = null) { var asm = MelonAssembly.LoadMelonAssembly(filepath); @@ -91,7 +91,7 @@ public static void LoadFromFile(string filepath, string symbolspath = null) MelonBase.RegisterSorted(asm.LoadedMelons); } - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead. This will be removed in a future version.", true)] public static void LoadFromByteArray(byte[] filedata, byte[] symbolsdata = null, string filepath = null) { var asm = MelonAssembly.LoadRawMelonAssembly(filepath, filedata, symbolsdata); @@ -101,7 +101,7 @@ public static void LoadFromByteArray(byte[] filedata, byte[] symbolsdata = null, MelonBase.RegisterSorted(asm.LoadedMelons); } - [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead.")] + [Obsolete("Use 'MelonBase.Load' and 'MelonBase.Register' instead. This will be removed in a future version.", true)] public static void LoadFromAssembly(Assembly asm, string filepath = null) { var ma = MelonAssembly.LoadMelonAssembly(filepath, asm); diff --git a/MelonLoader/MelonLaunchOptions.cs b/MelonLoader/MelonLaunchOptions.cs index f8a50c146..b2dbb5d11 100644 --- a/MelonLoader/MelonLaunchOptions.cs +++ b/MelonLoader/MelonLaunchOptions.cs @@ -97,11 +97,11 @@ internal static void Load() #region Obsolete - [Obsolete("Use LoaderConfig.Current.Loader instead.")] + [Obsolete("Use LoaderConfig.Current.Loader instead. This will be removed in a future version.", true)] [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's deprecated")] public static class Core { - [Obsolete("This option isn't used anymore.")] + [Obsolete("This option isn't used anymore. This will be removed in a future version.", true)] public enum LoadModeEnum { NORMAL, @@ -109,32 +109,32 @@ public enum LoadModeEnum BOTH } - [Obsolete("This option isn't used anymore. It will always return NORMAL.")] + [Obsolete("This option isn't used anymore. It will always return NORMAL. This will be removed in a future version.", true)] public static LoadModeEnum LoadMode_Plugins => LoadModeEnum.NORMAL; - [Obsolete("This option isn't used anymore. It will always return NORMAL.")] + [Obsolete("This option isn't used anymore. It will always return NORMAL. This will be removed in a future version.", true)] public static LoadModeEnum LoadMode_Mods => LoadModeEnum.NORMAL; - [Obsolete("Use LoaderConfig.Current.Loader.ForceQuit instead.")] + [Obsolete("Use LoaderConfig.Current.Loader.ForceQuit instead. This will be removed in a future version.", true)] public static bool QuitFix => LoaderConfig.Current.Loader.ForceQuit; - [Obsolete("Use LoaderConfig.Current.Loader.DisableStartScreen instead.")] + [Obsolete("Use LoaderConfig.Current.Loader.DisableStartScreen instead. This will be removed in a future version.", true)] public static bool StartScreen => !LoaderConfig.Current.Loader.DisableStartScreen; - [Obsolete("Use LoaderConfig.Current.UnityEngine.VersionOverride instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.VersionOverride instead. This will be removed in a future version.", true)] public static string UnityVersion => LoaderConfig.Current.UnityEngine.VersionOverride; - [Obsolete("Use LoaderConfig.Current.Loader.DebugMode instead.")] + [Obsolete("Use LoaderConfig.Current.Loader.DebugMode instead. This will be removed in a future version.", true)] public static bool IsDebug => LoaderConfig.Current.Loader.DebugMode; - [Obsolete("Use LoaderConfig.Current.Loader.LaunchDebugger instead.")] + [Obsolete("Use LoaderConfig.Current.Loader.LaunchDebugger instead. This will be removed in a future version.", true)] public static bool UserWantsDebugger => LoaderConfig.Current.Loader.LaunchDebugger; } - [Obsolete("Use LoaderConfig.Current.Console instead.")] + [Obsolete("Use LoaderConfig.Current.Console instead. This will be removed in a future version.", true)] public static class Console { - [Obsolete("Use LoaderConfig.CoreConfig.LoaderTheme instead.")] + [Obsolete("Use LoaderConfig.CoreConfig.LoaderTheme instead. This will be removed in a future version.", true)] public enum DisplayMode { NORMAL, @@ -144,62 +144,62 @@ public enum DisplayMode LEMON }; - [Obsolete("Use LoaderConfig.Current.Loader.Theme instead.")] + [Obsolete("Use LoaderConfig.Current.Loader.Theme instead. This will be removed in a future version.", true)] public static DisplayMode Mode => (DisplayMode)LoaderConfig.Current.Loader.Theme; - [Obsolete("Use LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner instead. This will be removed in a future version.", true)] public static bool CleanUnityLogs => !LoaderConfig.Current.UnityEngine.DisableConsoleLogCleaner; - [Obsolete("Use LoaderConfig.Current.Console.DontSetTitle instead.")] + [Obsolete("Use LoaderConfig.Current.Console.DontSetTitle instead. This will be removed in a future version.", true)] public static bool ShouldSetTitle => !LoaderConfig.Current.Console.DontSetTitle; - [Obsolete("Use LoaderConfig.Current.Console.AlwaysOnTop instead.")] + [Obsolete("Use LoaderConfig.Current.Console.AlwaysOnTop instead. This will be removed in a future version.", true)] public static bool AlwaysOnTop => LoaderConfig.Current.Console.AlwaysOnTop; - [Obsolete("Use LoaderConfig.Current.Console.Hide instead.")] + [Obsolete("Use LoaderConfig.Current.Console.Hide instead. This will be removed in a future version.", true)] public static bool ShouldHide => LoaderConfig.Current.Console.Hide; - [Obsolete("Use LoaderConfig.Current.Console.HideWarnings instead.")] + [Obsolete("Use LoaderConfig.Current.Console.HideWarnings instead. This will be removed in a future version.", true)] public static bool HideWarnings => LoaderConfig.Current.Console.HideWarnings; } - [Obsolete("Use LoaderConfig.Current.UnityEngine instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine instead. This will be removed in a future version.", true)] public static class Cpp2IL { - [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer instead. This will be removed in a future version.", true)] public static bool CallAnalyzer => LoaderConfig.Current.UnityEngine.EnableCpp2ILCallAnalyzer; - [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector instead. This will be removed in a future version.", true)] public static bool NativeMethodDetector => LoaderConfig.Current.UnityEngine.EnableCpp2ILNativeMethodDetector; } - [Obsolete("Use LoaderConfig.Current.UnityEngine instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine instead. This will be removed in a future version.", true)] public static class Il2CppAssemblyGenerator { - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceRegeneration instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceRegeneration instead. This will be removed in a future version.", true)] public static bool ForceRegeneration => LoaderConfig.Current.UnityEngine.ForceRegeneration; - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceOfflineGeneration instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceOfflineGeneration instead. This will be removed in a future version.", true)] public static bool OfflineMode => LoaderConfig.Current.UnityEngine.ForceOfflineGeneration; - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion instead. This will be removed in a future version.", true)] [SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores", Justification = "It's deprecated")] public static string ForceVersion_Dumper => LoaderConfig.Current.UnityEngine.ForceIl2CppDumperVersion; - [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceGeneratorRegex instead.")] + [Obsolete("Use LoaderConfig.Current.UnityEngine.ForceGeneratorRegex instead. This will be removed in a future version.", true)] public static string ForceRegex => LoaderConfig.Current.UnityEngine.ForceGeneratorRegex; } - [Obsolete("Use LoaderConfig.Logs instead.")] + [Obsolete("Use LoaderConfig.Logs instead. This will be removed in a future version.", true)] public static class Logger { - [Obsolete("Use LoaderConfig.Current.Logs.MaxLogs instead.")] + [Obsolete("Use LoaderConfig.Current.Logs.MaxLogs instead. This will be removed in a future version.", true)] public static int MaxLogs => (int)LoaderConfig.Current.Logs.MaxLogs; - [Obsolete("This option isn't used anymore. It will always return 10.")] + [Obsolete("This option isn't used anymore. It will always return 10. This will be removed in a future version.", true)] public static int MaxWarnings => 10; - [Obsolete("This option isn't used anymore. It will always return 10.")] + [Obsolete("This option isn't used anymore. It will always return 10. This will be removed in a future version.", true)] public static int MaxErrors => 10; } diff --git a/MelonLoader/MelonLogger.cs b/MelonLoader/MelonLogger.cs index 8478b677f..3272a72b2 100644 --- a/MelonLoader/MelonLogger.cs +++ b/MelonLoader/MelonLogger.cs @@ -144,7 +144,7 @@ internal static void RunMsgCallbacks(Color namesection_color, Color textColor, s MsgDrawingCallbackHandler?.Invoke(namesection_color, textColor, namesection, txt); } - [Obsolete("MsgCallbackHandler is obsolete. Please use MsgDrawingCallbackHandler for full Color support.")] + [Obsolete("MsgCallbackHandler is obsolete. Please use MsgDrawingCallbackHandler for full Color support. This will be removed in a future version.", true)] public static event Action MsgCallbackHandler; public static event Action MsgDrawingCallbackHandler; @@ -161,7 +161,7 @@ public class Instance { private readonly string Name = null; - [Obsolete("Color is obsolete. Please use DrawingColor for full Color support.")] + [Obsolete("Color is obsolete. Please use DrawingColor for full Color support. This will be removed in a future version.", true)] private ConsoleColor Color { get => DrawingColorToConsoleColor(DrawingColor); @@ -172,7 +172,7 @@ private ConsoleColor Color public Instance(string name) => Name = name?.Replace(" ", "_"); - [Obsolete("ConsoleColor is obsolete, use the (string, Color) constructor instead.")] + [Obsolete("ConsoleColor is obsolete, use the (string, Color) constructor instead. This will be removed in a future version.", true)] public Instance(string name, ConsoleColor color) : this(name) => Color = color; public Instance(string name, Color color) : this(name) => DrawingColor = color; @@ -318,33 +318,33 @@ internal static unsafe void PassLogMelonInfo(ColorRGB nameColor, string name, st } } - [Obsolete("Log is obsolete. Please use Msg instead.")] + [Obsolete("Log is obsolete. Please use Msg instead. This will be removed in a future version.", true)] public static void Log(string txt) => Msg(txt); - [Obsolete("Log is obsolete. Please use Msg instead.")] + [Obsolete("Log is obsolete. Please use Msg instead. This will be removed in a future version.", true)] public static void Log(string txt, params object[] args) => Msg(txt, args); - [Obsolete("Log is obsolete. Please use Msg instead.")] + [Obsolete("Log is obsolete. Please use Msg instead. This will be removed in a future version.", true)] public static void Log(object obj) => Msg(obj); - [Obsolete("Log is obsolete. Please use Msg instead.")] + [Obsolete("Log is obsolete. Please use Msg instead. This will be removed in a future version.", true)] public static void Log(ConsoleColor color, string txt) => Msg(color, txt); - [Obsolete("Log is obsolete. Please use Msg instead.")] + [Obsolete("Log is obsolete. Please use Msg instead. This will be removed in a future version.", true)] public static void Log(ConsoleColor color, string txt, params object[] args) => Msg(color, txt, args); - [Obsolete("Log is obsolete. Please use Msg instead.")] + [Obsolete("Log is obsolete. Please use Msg instead. This will be removed in a future version.", true)] public static void Log(ConsoleColor color, object obj) => Msg(color, obj); - [Obsolete("LogWarning is obsolete. Please use Warning instead.")] + [Obsolete("LogWarning is obsolete. Please use Warning instead. This will be removed in a future version.", true)] public static void LogWarning(string txt) => Warning(txt); - [Obsolete("LogWarning is obsolete. Please use Warning instead.")] + [Obsolete("LogWarning is obsolete. Please use Warning instead. This will be removed in a future version.", true)] public static void LogWarning(string txt, params object[] args) => Warning(txt, args); - [Obsolete("LogError is obsolete. Please use Error instead.")] + [Obsolete("LogError is obsolete. Please use Error instead. This will be removed in a future version.", true)] public static void LogError(string txt) => Error(txt); - [Obsolete("LogError is obsolete. Please use Error instead.")] + [Obsolete("LogError is obsolete. Please use Error instead. This will be removed in a future version.", true)] public static void LogError(string txt, params object[] args) => Error(txt, args); } \ No newline at end of file diff --git a/MelonLoader/MelonMod.cs b/MelonLoader/MelonMod.cs index c8d3f5dd3..0536924db 100644 --- a/MelonLoader/MelonMod.cs +++ b/MelonLoader/MelonMod.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -#pragma warning disable 0618 // Disabling the obsolete references warning to prevent the IDE going crazy when subscribing deprecated methods to some events in RegisterCallbacks +using System.Diagnostics.CodeAnalysis; namespace MelonLoader; @@ -19,7 +19,7 @@ private protected override bool RegisterInternal() } catch (Exception ex) { - MelonLogger.Error($"Failed to register {MelonTypeName} '{Location}': Melon failed to initialize in the deprecated OnPreSupportModule callback!"); + MelonLogger.Error($"Failed to register {MelonTypeName} '{MelonAssembly.Location}': Melon failed to initialize in the deprecated OnPreSupportModule callback!"); MelonLogger.Error(ex.ToString()); return false; } @@ -48,6 +48,14 @@ private protected override void RegisterCallbacks() MelonEvents.OnSceneWasInitialized.Subscribe(OnSceneWasInitialized, Priority); MelonEvents.OnSceneWasUnloaded.Subscribe(OnSceneWasUnloaded, Priority); +#pragma warning disable CS0618 // Type or member is obsolete + RegisterObsoleteCallbacks(); +#pragma warning restore CS0618 // Type or member is obsolete + } + + [Obsolete("Used to make obsolete callbacks still function.")] + private void RegisterObsoleteCallbacks() + { MelonEvents.OnSceneWasLoaded.Subscribe((idx, name) => OnLevelWasLoaded(idx), Priority); MelonEvents.OnSceneWasInitialized.Subscribe((idx, name) => OnLevelWasInitialized(idx), Priority); MelonEvents.OnApplicationStart.Subscribe(OnApplicationStart, Priority); @@ -73,14 +81,14 @@ public virtual void OnSceneWasUnloaded(int buildIndex, string sceneName) { } #endregion #region Obsolete Members - [Obsolete("Override OnSceneWasLoaded instead.")] + [Obsolete("Override OnSceneWasLoaded instead. This will be removed in a future version.", true)] public virtual void OnLevelWasLoaded(int level) { } - [Obsolete("Override OnSceneWasInitialized instead.")] + [Obsolete("Override OnSceneWasInitialized instead. This will be removed in a future version.", true)] public virtual void OnLevelWasInitialized(int level) { } - [Obsolete()] + [Obsolete] private MelonModInfoAttribute _LegacyInfoAttribute = null; - [Obsolete("Use MelonBase.Info instead.")] + [Obsolete("Use MelonBase.Info instead. This will be removed in a future version.", true)] public MelonModInfoAttribute InfoAttribute { get @@ -91,7 +99,7 @@ public MelonModInfoAttribute InfoAttribute } [Obsolete()] private MelonModGameAttribute[] _LegacyGameAttributes = null; - [Obsolete("Use MelonBase.Games instead.")] + [Obsolete("Use MelonBase.Games instead. This will be removed in a future version.", true)] public MelonModGameAttribute[] GameAttributes { get diff --git a/MelonLoader/MelonPlugin.cs b/MelonLoader/MelonPlugin.cs index 0cb9d97ae..b68bd16a0 100644 --- a/MelonLoader/MelonPlugin.cs +++ b/MelonLoader/MelonPlugin.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -#pragma warning disable 0618 // Disabling the obsolete references warning to prevent the IDE going crazy when subscribing deprecated methods to some events in RegisterCallbacks namespace MelonLoader; @@ -17,12 +16,22 @@ private protected override void RegisterCallbacks() MelonEvents.OnPreInitialization.Subscribe(OnPreInitialization, Priority); MelonEvents.OnApplicationEarlyStart.Subscribe(OnApplicationEarlyStart, Priority); + +#pragma warning disable CS0618 // Type or member is obsolete + RegisterObsoleteCallbacks(); +#pragma warning restore CS0618 // Type or member is obsolete + MelonEvents.OnPreModsLoaded.Subscribe(OnPreModsLoaded, Priority); - MelonEvents.OnPreModsLoaded.Subscribe(OnApplicationStart, Priority); MelonEvents.OnApplicationStart.Subscribe(OnApplicationStarted, Priority); MelonEvents.OnPreSupportModule.Subscribe(OnPreSupportModule, Priority); } + [Obsolete("Used to make obsolete callbacks still function.")] + private void RegisterObsoleteCallbacks() + { + MelonEvents.OnPreModsLoaded.Subscribe(OnApplicationStart, Priority); + } + private protected override bool RegisterInternal() { if (!base.RegisterInternal()) @@ -69,7 +78,7 @@ public virtual void OnApplicationStarted() { } [Obsolete()] private MelonPluginInfoAttribute _LegacyInfoAttribute = null; - [Obsolete("MelonPlugin.InfoAttribute is obsolete. Please use MelonBase.Info instead.")] + [Obsolete("MelonPlugin.InfoAttribute is obsolete. Please use MelonBase.Info instead. This will be removed in a future version.", true)] public MelonPluginInfoAttribute InfoAttribute { get @@ -80,7 +89,7 @@ public MelonPluginInfoAttribute InfoAttribute } [Obsolete()] private MelonPluginGameAttribute[] _LegacyGameAttributes = null; - [Obsolete("MelonPlugin.GameAttributes is obsolete. Please use MelonBase.Games instead.")] + [Obsolete("MelonPlugin.GameAttributes is obsolete. Please use MelonBase.Games instead. This will be removed in a future version.", true)] public MelonPluginGameAttribute[] GameAttributes { get diff --git a/MelonLoader/MelonPreferences_Entry.cs b/MelonLoader/MelonPreferences_Entry.cs index fd4209553..e0b049da1 100644 --- a/MelonLoader/MelonPreferences_Entry.cs +++ b/MelonLoader/MelonPreferences_Entry.cs @@ -43,7 +43,7 @@ protected void FireUntypedValueChanged(object old, object neew) OnValueChangedUntyped?.Invoke(); } - [Obsolete("Please use the OnEntryValueChangedUntyped MelonEvent instead.")] + [Obsolete("Please use the OnEntryValueChangedUntyped MelonEvent instead. This will be removed in a future version.", true)] public event Action OnValueChangedUntyped; } @@ -90,7 +90,7 @@ public override object BoxedEditedValue public readonly MelonEvent OnEntryValueChanged = new(); - [Obsolete("Please use the OnEntryValueChanged MelonEvent instead.")] + [Obsolete("Please use the OnEntryValueChanged MelonEvent instead. This will be removed in a future version.", true)] public event Action OnValueChanged; public override Type GetReflectedType() => typeof(T); @@ -124,8 +124,6 @@ public override TomlValue Save() var returnval = TomletMain.ValueFrom(Value); returnval.Comments.PrecedingComment = Description; returnval.Comments.InlineComment = Comment; - if (!string.IsNullOrEmpty(returnval.Comments.InlineComment)) - returnval.Comments.InlineComment.Replace('\n', ' '); return returnval; } } \ No newline at end of file diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs index 1c76b378d..2df62cc24 100644 --- a/MelonLoader/MelonUtils.cs +++ b/MelonLoader/MelonUtils.cs @@ -18,8 +18,6 @@ using System.Security.Cryptography; using System.Text; -#pragma warning disable 0618 - namespace MelonLoader; public static class MelonUtils @@ -55,15 +53,15 @@ internal static void Setup(AppDomain domain) CurrentDomain = IsGameIl2Cpp() ? MelonPlatformDomainAttribute.CompatibleDomains.IL2CPP : MelonPlatformDomainAttribute.CompatibleDomains.MONO; } - [Obsolete("Use MelonEnvironment.MelonBaseDirectory instead")] + [Obsolete("Use MelonEnvironment.MelonBaseDirectory instead. This will be removed in a future version.", true)] public static string BaseDirectory => MelonEnvironment.MelonBaseDirectory; - [Obsolete("Use MelonEnvironment.GameRootDirectory instead")] + [Obsolete("Use MelonEnvironment.GameRootDirectory instead. This will be removed in a future version.", true)] public static string GameDirectory => MelonEnvironment.GameRootDirectory; - [Obsolete("Use MelonEnvironment.MelonLoaderDirectory instead")] + [Obsolete("Use MelonEnvironment.MelonLoaderDirectory instead. This will be removed in a future version.", true)] public static string MelonLoaderDirectory => MelonEnvironment.MelonLoaderDirectory; - [Obsolete("Use MelonEnvironment.UserDataDirectory instead")] + [Obsolete("Use MelonEnvironment.UserDataDirectory instead. This will be removed in a future version.", true)] public static string UserDataDirectory => MelonEnvironment.UserDataDirectory; - [Obsolete("Use MelonEnvironment.UserLibsDirectory instead")] + [Obsolete("Use MelonEnvironment.UserLibsDirectory instead. This will be removed in a future version.", true)] public static string UserLibsDirectory => MelonEnvironment.UserLibsDirectory; public static MelonPlatformAttribute.CompatiblePlatforms CurrentPlatform { get; private set; } public static MelonPlatformDomainAttribute.CompatibleDomains CurrentDomain { get; private set; } @@ -121,7 +119,7 @@ public static void SetCurrentDomainBaseDirectory(string dirpath, AppDomain domai try { ((AppDomainSetup)typeof(AppDomain).GetProperty("SetupInformationNoCopy", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(domain, new object[0])) + .GetValue(domain, [])) .SetApplicationBase(dirpath); } catch (Exception ex) @@ -171,10 +169,10 @@ private static MelonBase CheckForMelonInFrame(StackTrace st, int frame = 0) private static MelonBase CheckForMelonInFrame(StackFrame sf) //The JIT compiler on .NET 6 on Windows 10 (win11 is fine, somehow) really doesn't like us calling StackFrame.GetMethod here //Rather than trying to work out why, I'm just going to call it via reflection. - => GetMelonFromAssembly(((MethodBase)StackFrameGetMethod.Invoke(sf, new object[0]))?.DeclaringType?.Assembly); + => GetMelonFromAssembly(((MethodBase)StackFrameGetMethod.Invoke(sf, []))?.DeclaringType?.Assembly); private static MelonBase GetMelonFromAssembly(Assembly asm) - => asm == null ? null : MelonHandler.Plugins.Cast().FirstOrDefault(x => x.Assembly == asm) ?? MelonHandler.Mods.FirstOrDefault(x => x.Assembly == asm); + => asm == null ? null : MelonPlugin.RegisteredMelons.Cast().FirstOrDefault(x => x.MelonAssembly.Assembly == asm) ?? MelonMod.RegisteredMelons.FirstOrDefault(x => x.MelonAssembly.Assembly == asm); public static string ComputeSimpleSHA256Hash(string filePath) { @@ -198,7 +196,7 @@ public static string ToString(this byte[] data) { var result = new StringBuilder(); for (var i = 0; i < data.Length; i++) - result.Append(data[i].ToString()); + result.Append(data[i]); return result.ToString(); } @@ -288,7 +286,7 @@ public static T[] PullAttributesFromAssembly(Assembly asm, bool inherit = fal output.Add(att as T); } - return output.ToArray(); + return [.. output]; } public static bool IsTypeEqualToName(Type type1, string type2) @@ -460,13 +458,13 @@ public static ClassPackageFile LoadIncludedClassPackage(this AssetsManager asset return classPackage; } - [Obsolete("MelonLoader.MelonUtils.GetUnityVersion() is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead.")] + [Obsolete("MelonLoader.MelonUtils.GetUnityVersion() is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.EngineVersion instead. This will be removed in a future version.", true)] public static string GetUnityVersion() => UnityInformationHandler.EngineVersion.ToStringWithoutType(); - [Obsolete("MelonLoader.MelonUtils.GameDeveloper is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead.")] + [Obsolete("MelonLoader.MelonUtils.GameDeveloper is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameDeveloper instead. This will be removed in a future version.", true)] public static string GameDeveloper { get => UnityInformationHandler.GameDeveloper; } - [Obsolete("MelonLoader.MelonUtils.GameName is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead.")] + [Obsolete("MelonLoader.MelonUtils.GameName is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameName instead. This will be removed in a future version.", true)] public static string GameName { get => UnityInformationHandler.GameName; } - [Obsolete("MelonLoader.MelonUtils.GameVersion is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameVersion instead.")] + [Obsolete("MelonLoader.MelonUtils.GameVersion is obsolete. Please use MelonLoader.InternalUtils.UnityInformationHandler.GameVersion instead. This will be removed in a future version.", true)] public static string GameVersion { get => UnityInformationHandler.GameVersion; } public static unsafe bool IsGame32Bit() => @@ -483,21 +481,22 @@ public static bool IsOldMono() => File.Exists(MelonEnvironment.UnityGameDataDire public static bool IsUnderWineOrSteamProton() => WineGetVersion is not null; - [Obsolete("Use MelonEnvironment.GameExecutablePath instead")] + [Obsolete("Use MelonEnvironment.GameExecutablePath instead. This will be removed in a future version.", true)] public static string GetApplicationPath() => MelonEnvironment.GameExecutablePath; - [Obsolete("Use MelonEnvironment.UnityGameDataDirectory instead")] + [Obsolete("Use MelonEnvironment.UnityGameDataDirectory instead. This will be removed in a future version.", true)] public static string GetGameDataDirectory() => MelonEnvironment.UnityGameDataDirectory; - [Obsolete("Use MelonEnvironment.MelonManagedDirectory instead")] + [Obsolete("Use MelonEnvironment.MelonManagedDirectory instead. This will be removed in a future version.", true)] public static string GetManagedDirectory() => MelonEnvironment.MelonManagedDirectory; public static void SetConsoleTitle(string title) { - if (!MelonLaunchOptions.Console.ShouldSetTitle || MelonLaunchOptions.Console.ShouldHide) + if (LoaderConfig.Current.Console.DontSetTitle || !BootstrapInterop.Library.IsConsoleOpen()) return; - Console.Title = title; + // Using reflection to avoid resolver errors + AccessTools.Property(typeof(Console), "Title")?.SetValue(null, title, null); } public static string GetFileProductName(string filepath) @@ -544,7 +543,7 @@ internal static void SetupWineCheck() internal static extern uint RtlGetVersion(out OsVersionInfo versionInformation); // return type should be the NtStatus enum [StructLayout(LayoutKind.Sequential)] - internal struct OsVersionInfo + internal readonly struct OsVersionInfo { private readonly uint OsVersionInfoSize; @@ -566,7 +565,8 @@ internal static string GetOSVersion() if (IsUnderWineOrSteamProton()) return $"Wine {WineGetVersion()}"; - RtlGetVersion(out var versionInformation); + + _ = RtlGetVersion(out var versionInformation); var minor = versionInformation.MinorVersion; var build = versionInformation.BuildNumber; @@ -606,13 +606,13 @@ internal static string GetOSVersion() return $"{versionString}"; } - [Obsolete("Use NativeUtils.NativeHook instead")] + [Obsolete("Use NativeUtils.NativeHook instead. This will be removed in a future version.", true)] public static void NativeHookAttach(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookAttach(target, detour); - [Obsolete("Use NativeUtils.NativeHook instead")] + [Obsolete("Use NativeUtils.NativeHook instead. This will be removed in a future version.", true)] internal static void NativeHookAttachDirect(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookAttachDirect(target, detour); - [Obsolete("Use NativeUtils.NativeHook instead")] + [Obsolete("Use NativeUtils.NativeHook instead. This will be removed in a future version.", true)] public static void NativeHookDetach(IntPtr target, IntPtr detour) => BootstrapInterop.NativeHookDetach(target, detour); //Removing these as they're private so mods shouldn't need them diff --git a/MelonLoader/Resolver/MelonAssemblyResolver.cs b/MelonLoader/Resolver/MelonAssemblyResolver.cs index 1595538b3..a86954eb7 100644 --- a/MelonLoader/Resolver/MelonAssemblyResolver.cs +++ b/MelonLoader/Resolver/MelonAssemblyResolver.cs @@ -7,8 +7,6 @@ using System.Runtime.Loader; #endif -#pragma warning disable CS0618 // Type or member is obsolete - namespace MelonLoader.Resolver; public class MelonAssemblyResolver @@ -108,17 +106,28 @@ public static void RemoveSearchDirectory(string path) // Assembly public delegate void OnAssemblyLoadHandler(Assembly assembly); public static event OnAssemblyLoadHandler OnAssemblyLoad; + internal static void SafeInvoke_OnAssemblyLoad(Assembly assembly) { #if !NET6_0_OR_GREATER - // Backwards Compatibility - MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyLoad(assembly); +#pragma warning disable CS0618 // Type or member is obsolete + InvokeObsoleteOnAssemblyLoad(assembly); +#pragma warning restore CS0618 // Type or member is obsolete #endif OnAssemblyLoad?.Invoke(assembly); } +#if !NET6_0_OR_GREATER + [Obsolete("Used to make the obsolete event still function.")] + private static void InvokeObsoleteOnAssemblyLoad(Assembly assembly) + { + MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyLoad(assembly); + } +#endif + public delegate Assembly OnAssemblyResolveHandler(string name, Version version); public static event OnAssemblyResolveHandler OnAssemblyResolve; + internal static Assembly SafeInvoke_OnAssemblyResolve(string name, Version version) { #if NET6_0_OR_GREATER @@ -127,14 +136,24 @@ internal static Assembly SafeInvoke_OnAssemblyResolve(string name, Version versi #else - // Backwards Compatibility - var assembly = MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyResolve(name, version); +#pragma warning disable CS0618 // Type or member is obsolete + var assembly = InvokeObsoleteOnAssemblyResolve(name, version); +#pragma warning restore CS0618 // Type or member is obsolete + assembly ??= OnAssemblyResolve?.Invoke(name, version); return assembly; #endif } +#if !NET6_0_OR_GREATER + [Obsolete("Used to make the obsolete event still function.")] + private static Assembly InvokeObsoleteOnAssemblyResolve(string name, Version version) + { + return MonoInternals.MonoResolveManager.SafeInvoke_OnAssemblyResolve(name, version); + } +#endif + public static AssemblyResolveInfo GetAssemblyResolveInfo(string name) => AssemblyManager.GetInfo(name); public static void LoadInfoFromAssembly(Assembly assembly) diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs index 417ec129e..1e71a5d4f 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundle.cs @@ -35,7 +35,7 @@ public bool IsStreamedSceneAssetBundle } } - [Obsolete("Use IsStreamedSceneAssetBundle (starting with upper-case) instead.", true)] + [Obsolete("Use IsStreamedSceneAssetBundle (starting with upper-case) instead. This will be removed in a future version.", true)] [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] public bool isStreamedSceneAssetBundle => IsStreamedSceneAssetBundle; @@ -52,7 +52,7 @@ public Object MainAsset } } - [Obsolete("Use MainAsset (starting with upper-case) instead.", true)] + [Obsolete("Use MainAsset (starting with upper-case) instead. This will be removed in a future version.", true)] [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] public Object mainAsset => MainAsset; diff --git a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs index 6cfbbbe67..90633c58b 100644 --- a/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs +++ b/UnityUtilities/UnityEngine.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs @@ -25,7 +25,7 @@ public Il2CppAssetBundle AssetBundle } } - [Obsolete("Use AssetBundle (starting with upper-case) instead.", true)] + [Obsolete("Use AssetBundle (starting with upper-case) instead. This will be removed in a future version.", true)] [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] public Il2CppAssetBundle assetBundle => AssetBundle; @@ -53,7 +53,7 @@ public Object Asset } } - [Obsolete("Use Asset (starting with upper-case) instead.", true)] + [Obsolete("Use Asset (starting with upper-case) instead. This will be removed in a future version.", true)] [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] public Object asset => Asset; @@ -66,7 +66,7 @@ public Il2CppReferenceArray AllAssets } } - [Obsolete("Use AllAssets (starting with upper-case) instead.", true)] + [Obsolete("Use AllAssets (starting with upper-case) instead. This will be removed in a future version.", true)] [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "It's deprecated")] public Il2CppReferenceArray allAssets => AllAssets; From 95c9b04749516bdbbfeca20d910fed216c2a358d Mon Sep 17 00:00:00 2001 From: slxdy Date: Thu, 23 Jan 2025 12:45:38 +0100 Subject: [PATCH 18/18] Revert ISupportModule changes --- Dependencies/SupportModules/Il2Cpp/Main.cs | 5 ++--- Dependencies/SupportModules/Mono/Main.cs | 7 +++---- Dependencies/SupportModules/SupportModule_To.cs | 5 ++--- .../ISupportModule_From.cs | 6 +++--- .../ISupportModule_To.cs | 7 +++---- MelonLoader/MelonLaunchOptions.cs | 3 ++- MelonLoader/Modules/ISupportModuleFrom.cs | 16 ---------------- MelonLoader/Modules/ISupportModuleTo.cs | 10 ---------- MelonLoader/SupportModule.cs | 7 +++---- MelonLoader/SupportModule_From.cs | 6 ++---- 10 files changed, 20 insertions(+), 52 deletions(-) rename MelonLoader/{BackwardsCompatibility => }/ISupportModule_From.cs (75%) rename MelonLoader/{BackwardsCompatibility => }/ISupportModule_To.cs (52%) delete mode 100644 MelonLoader/Modules/ISupportModuleFrom.cs delete mode 100644 MelonLoader/Modules/ISupportModuleTo.cs diff --git a/Dependencies/SupportModules/Il2Cpp/Main.cs b/Dependencies/SupportModules/Il2Cpp/Main.cs index 9ff4935a3..e8f071cf0 100644 --- a/Dependencies/SupportModules/Il2Cpp/Main.cs +++ b/Dependencies/SupportModules/Il2Cpp/Main.cs @@ -4,7 +4,6 @@ using Il2CppInterop.Runtime.Startup; using MelonLoader.CoreClrUtils; using MelonLoader.InternalUtils; -using MelonLoader.Modules; using MelonLoader.Support.Preferences; using MelonLoader.Utils; using Microsoft.Extensions.Logging; @@ -20,7 +19,7 @@ namespace MelonLoader.Support; internal static class Main { - internal static ISupportModuleFrom Interface; + internal static ISupportModule_From Interface; internal static InteropInterface Interop; internal static GameObject obj = null; internal static SM_Component component = null; @@ -28,7 +27,7 @@ internal static class Main private static Assembly Il2Cppmscorlib = null; private static Type streamType = null; - private static ISupportModuleTo Initialize(ISupportModuleFrom interface_from) + private static ISupportModule_To Initialize(ISupportModule_From interface_from) { Interface = interface_from; diff --git a/Dependencies/SupportModules/Mono/Main.cs b/Dependencies/SupportModules/Mono/Main.cs index fe1ab009e..f1d63e7b9 100644 --- a/Dependencies/SupportModules/Mono/Main.cs +++ b/Dependencies/SupportModules/Mono/Main.cs @@ -1,5 +1,4 @@ -using MelonLoader.Modules; -using MelonLoader.Support.Preferences; +using MelonLoader.Support.Preferences; using System.Reflection; using UnityEngine; @@ -9,11 +8,11 @@ namespace MelonLoader.Support; internal static class Main { - internal static ISupportModuleFrom Interface = null; + internal static ISupportModule_From Interface = null; internal static GameObject obj = null; internal static SM_Component component = null; - private static ISupportModuleTo Initialize(ISupportModuleFrom interface_from) + private static ISupportModule_To Initialize(ISupportModule_From interface_from) { Interface = interface_from; UnityMappers.RegisterMappers(); diff --git a/Dependencies/SupportModules/SupportModule_To.cs b/Dependencies/SupportModules/SupportModule_To.cs index 8e09e405e..d6cef384d 100644 --- a/Dependencies/SupportModules/SupportModule_To.cs +++ b/Dependencies/SupportModules/SupportModule_To.cs @@ -1,11 +1,10 @@ -using MelonLoader.Modules; -using System.Collections; +using System.Collections; using System.Collections.Generic; using UnityEngine; namespace MelonLoader.Support; -internal class SupportModule_To : ISupportModuleTo +internal class SupportModule_To : ISupportModule_To { internal static readonly List QueuedCoroutines = []; public object StartCoroutine(IEnumerator coroutine) diff --git a/MelonLoader/BackwardsCompatibility/ISupportModule_From.cs b/MelonLoader/ISupportModule_From.cs similarity index 75% rename from MelonLoader/BackwardsCompatibility/ISupportModule_From.cs rename to MelonLoader/ISupportModule_From.cs index cafd03b37..dedef9c10 100644 --- a/MelonLoader/BackwardsCompatibility/ISupportModule_From.cs +++ b/MelonLoader/ISupportModule_From.cs @@ -1,8 +1,8 @@ -using System; +using System.Diagnostics.CodeAnalysis; namespace MelonLoader; -[Obsolete("Why is this public???", true)] +[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "It's public API")] public interface ISupportModule_From { void OnApplicationLateStart(); @@ -16,4 +16,4 @@ public interface ISupportModule_From void Quit(); void DefiniteQuit(); void SetInteropSupportInterface(InteropSupport.Interface interop); -} +} \ No newline at end of file diff --git a/MelonLoader/BackwardsCompatibility/ISupportModule_To.cs b/MelonLoader/ISupportModule_To.cs similarity index 52% rename from MelonLoader/BackwardsCompatibility/ISupportModule_To.cs rename to MelonLoader/ISupportModule_To.cs index bc58aaf4b..4b45e9829 100644 --- a/MelonLoader/BackwardsCompatibility/ISupportModule_To.cs +++ b/MelonLoader/ISupportModule_To.cs @@ -1,12 +1,11 @@ -using System; -using System.Collections; +using System.Collections; namespace MelonLoader; -[Obsolete("Why is this public???", true)] +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "It's public API")] public interface ISupportModule_To { object StartCoroutine(IEnumerator coroutine); void StopCoroutine(object coroutineToken); void UnityDebugLog(string msg); -} +} \ No newline at end of file diff --git a/MelonLoader/MelonLaunchOptions.cs b/MelonLoader/MelonLaunchOptions.cs index b2dbb5d11..7cc5dab3d 100644 --- a/MelonLoader/MelonLaunchOptions.cs +++ b/MelonLoader/MelonLaunchOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -64,7 +65,7 @@ internal static void Load() // Parse Argument string cmdArg = null; - if (noPrefixCmd.Contains("=")) + if (noPrefixCmd.Contains('=')) { var split = noPrefixCmd.Split('='); noPrefixCmd = split[0]; diff --git a/MelonLoader/Modules/ISupportModuleFrom.cs b/MelonLoader/Modules/ISupportModuleFrom.cs deleted file mode 100644 index 68eca10b7..000000000 --- a/MelonLoader/Modules/ISupportModuleFrom.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MelonLoader.Modules; - -internal interface ISupportModuleFrom -{ - void OnApplicationLateStart(); - void OnSceneWasLoaded(int buildIndex, string sceneName); - void OnSceneWasInitialized(int buildIndex, string sceneName); - void OnSceneWasUnloaded(int buildIndex, string sceneName); - void Update(); - void FixedUpdate(); - void LateUpdate(); - void OnGUI(); - void Quit(); - void DefiniteQuit(); - void SetInteropSupportInterface(InteropSupport.Interface interop); -} \ No newline at end of file diff --git a/MelonLoader/Modules/ISupportModuleTo.cs b/MelonLoader/Modules/ISupportModuleTo.cs deleted file mode 100644 index 0217d4483..000000000 --- a/MelonLoader/Modules/ISupportModuleTo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections; - -namespace MelonLoader.Modules; - -internal interface ISupportModuleTo -{ - object StartCoroutine(IEnumerator coroutine); - void StopCoroutine(object coroutineToken); - void UnityDebugLog(string msg); -} \ No newline at end of file diff --git a/MelonLoader/SupportModule.cs b/MelonLoader/SupportModule.cs index f8931eeec..77138a793 100644 --- a/MelonLoader/SupportModule.cs +++ b/MelonLoader/SupportModule.cs @@ -1,5 +1,4 @@ -using MelonLoader.Modules; -using MelonLoader.Utils; +using MelonLoader.Utils; using System; using System.Collections.Generic; using System.IO; @@ -9,7 +8,7 @@ namespace MelonLoader; internal static class SupportModule { - internal static ISupportModuleTo Interface = null; + internal static ISupportModule_To Interface = null; private static string BaseDirectory = null; private static readonly List Modules = @@ -92,7 +91,7 @@ private static bool LoadInterface(string ModulePath) return false; } - Interface = (ISupportModuleTo)method.Invoke(null, [new SupportModule_From()]); + Interface = (ISupportModule_To)method.Invoke(null, [new SupportModule_From()]); if (Interface == null) { MelonLogger.Error("Failed to Initialize Interface!"); diff --git a/MelonLoader/SupportModule_From.cs b/MelonLoader/SupportModule_From.cs index d5196f4c8..55b764f9b 100644 --- a/MelonLoader/SupportModule_From.cs +++ b/MelonLoader/SupportModule_From.cs @@ -1,8 +1,6 @@ -using MelonLoader.Modules; +namespace MelonLoader; -namespace MelonLoader; - -internal class SupportModule_From : ISupportModuleFrom +internal class SupportModule_From : ISupportModule_From { public void OnApplicationLateStart() => MelonEvents.OnApplicationLateStart.Invoke();