Browse Source

Merge branch 'master' into Plugin-subfolder-support

pull/102/head
Arimodu 1 year ago
committed by GitHub
parent
commit
8b3842a754
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 946 additions and 768 deletions
  1. +246
    -0
      .editorconfig
  2. +1
    -0
      BSIPA.sln
  3. +2
    -2
      Common.props
  4. +9
    -2
      Doorstop/Proxy/main.c
  5. +2
    -0
      Doorstop/Proxy/mono.h
  6. +4
    -3
      IPA.Injector/GameVersionEarly.cs
  7. +19
    -11
      IPA.Injector/IPA.Injector.csproj
  8. +28
    -31
      IPA.Injector/Injector.cs
  9. +0
    -219
      IPA.Injector/Virtualizer.cs
  10. +145
    -150
      IPA.Loader/Config/Data/Value.cs
  11. +40
    -7
      IPA.Loader/Config/SelfConfig.cs
  12. +1
    -1
      IPA.Loader/Config/Stores/Converters.cs
  13. +1
    -0
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs
  14. +1
    -0
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs
  15. +1
    -1
      IPA.Loader/IPA.Loader.csproj
  16. +0
    -44
      IPA.Loader/Loader/HarmonyProtector.cs
  17. +13
    -4
      IPA.Loader/Loader/PluginLoader.cs
  18. +4
    -2
      IPA.Loader/Loader/PluginManager.cs
  19. +1
    -1
      IPA.Loader/Loader/manifest.json
  20. +31
    -53
      IPA.Loader/Logging/ConsoleWindow.cs
  21. +39
    -38
      IPA.Loader/Logging/LogPrinter.cs
  22. +159
    -116
      IPA.Loader/Logging/Printers/ColoredConsolePrinter.cs
  23. +34
    -22
      IPA.Loader/Logging/Printers/GZFilePrinter.cs
  24. +8
    -5
      IPA.Loader/Logging/StandardLogger.cs
  25. +1
    -1
      IPA.Loader/Logging/StdoutInterceptor.cs
  26. +1
    -1
      IPA.Loader/Utilities/AlmostVersion.cs
  27. +4
    -4
      IPA.Loader/Utilities/Async/Coroutines.cs
  28. +28
    -10
      IPA.Loader/Utilities/Async/UnityMainThreadTaskScheduler.cs
  29. +8
    -11
      IPA.Loader/Utilities/CriticalSection.cs
  30. +13
    -0
      IPA.Loader/Utilities/ReflectionUtil.cs
  31. +73
    -9
      IPA.Loader/Utilities/UnityGame.cs
  32. +1
    -1
      IPA/Program.cs
  33. +2
    -2
      README.md
  34. BIN
      Refs/UnityEngine.CoreModule.Net4.dll
  35. BIN
      Refs/UnityEngine.Net4.dll
  36. BIN
      Refs/UnityEngine.UnityWebRequestModule.Net4.dll
  37. +13
    -8
      docs/articles/command-line.md
  38. +10
    -6
      docs/articles/start-user.md
  39. +2
    -2
      docs/build.ps1
  40. +1
    -1
      docs/docfx.json

+ 246
- 0
.editorconfig View File

@ -0,0 +1,246 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:warning
dotnet_style_prefer_conditional_expression_over_return = true:warning
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_simplified_boolean_expressions = true:warning
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:suggestion
csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true:warning
csharp_style_prefer_pattern_matching = true:warning
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_local_over_anonymous_function = true:warning
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable:warning
csharp_style_unused_value_expression_statement_preference = discard_variable:warning
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:warning
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:warning
dotnet_style_prefer_conditional_expression_over_return = true:warning
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion

+ 1
- 0
BSIPA.sln View File

@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{C79C2C3A
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4D6639A2-BD39-4F9B-AF7F-8E5F3B88243D}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4D6639A2-BD39-4F9B-AF7F-8E5F3B88243D}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\build.yml = .github\workflows\build.yml .github\workflows\build.yml = .github\workflows\build.yml
Common.props = Common.props Common.props = Common.props
Common.targets = Common.targets Common.targets = Common.targets


+ 2
- 2
Common.props View File

@ -14,11 +14,11 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" />
<PackageReference Include="Nullable" Version="1.3.0">
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="IsExternalInit" Version="1.0.2">
<PackageReference Include="IsExternalInit" Version="1.0.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>


+ 9
- 2
Doorstop/Proxy/main.c View File

@ -111,6 +111,13 @@ void unhandledException(void* exc, void* data)
// We use this since it will always be called once to initialize Mono's JIT // We use this since it will always be called once to initialize Mono's JIT
void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version) void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_version)
{ {
const BOOL debugger_already_initialized = mono_debug_enabled();
if(debugger_already_initialized)
{
LOG("Debugger was already initialized\n");
}
// Call the original mono_jit_init_version to initialize the Unity Root Domain // Call the original mono_jit_init_version to initialize the Unity Root Domain
if (debug) { if (debug) {
char* opts[1]; char* opts[1];
@ -118,14 +125,14 @@ void *ownMonoJitInitVersion(const char *root_domain_name, const char *runtime_ve
ownMonoJitParseOptions(0, opts); ownMonoJitParseOptions(0, opts);
} }
#ifdef WIN32 #ifdef WIN32
if (debug_info) {
if (debug_info && !debugger_already_initialized) {
mono_debug_init(MONO_DEBUG_FORMAT_MONO); mono_debug_init(MONO_DEBUG_FORMAT_MONO);
} }
#endif #endif
void *domain = mono_jit_init_version(root_domain_name, runtime_version); void *domain = mono_jit_init_version(root_domain_name, runtime_version);
if (debug_info) {
if (debug_info && !debugger_already_initialized) {
#ifdef WIN64 #ifdef WIN64
mono_debug_init(MONO_DEBUG_FORMAT_MONO); mono_debug_init(MONO_DEBUG_FORMAT_MONO);
#endif #endif


+ 2
- 0
Doorstop/Proxy/mono.h View File

@ -40,6 +40,7 @@ typedef enum {
void (*mono_jit_parse_options)(int argc, char * argv[]); void (*mono_jit_parse_options)(int argc, char * argv[]);
void (*mono_debug_init)(MonoDebugFormat format); void (*mono_debug_init)(MonoDebugFormat format);
BOOL (*mono_debug_enabled)(void);
void (*mono_debug_domain_create)(void*); void (*mono_debug_domain_create)(void*);
void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version); void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version);
@ -90,6 +91,7 @@ inline void loadMonoFunctions(HMODULE monoLib)
GET_MONO_PROC(mono_assembly_get_image); GET_MONO_PROC(mono_assembly_get_image);
GET_MONO_PROC(mono_runtime_invoke); GET_MONO_PROC(mono_runtime_invoke);
GET_MONO_PROC(mono_debug_init); GET_MONO_PROC(mono_debug_init);
GET_MONO_PROC(mono_debug_enabled);
GET_MONO_PROC(mono_jit_init_version); GET_MONO_PROC(mono_jit_init_version);
GET_MONO_PROC(mono_jit_parse_options); GET_MONO_PROC(mono_jit_parse_options);
GET_MONO_PROC(mono_method_desc_new); GET_MONO_PROC(mono_method_desc_new);


+ 4
- 3
IPA.Injector/GameVersionEarly.cs View File

@ -1,4 +1,5 @@
using IPA.Utilities;
#nullable enable
using IPA.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -48,7 +49,7 @@ namespace IPA.Injector
} }
var rewind = -sizeof(int) - sizeof(byte); var rewind = -sizeof(int) - sizeof(byte);
stream.Seek(rewind, SeekOrigin.Current); // rewind to the string length
_ = stream.Seek(rewind, SeekOrigin.Current); // rewind to the string length
var strlen = reader.ReadInt32(); var strlen = reader.ReadInt32();
var strbytes = reader.ReadBytes(strlen); var strbytes = reader.ReadBytes(strlen);
@ -57,7 +58,7 @@ namespace IPA.Injector
} }
} }
internal static AlmostVersion SafeParseVersion() => new AlmostVersion(GetGameVersion());
internal static AlmostVersion SafeParseVersion() => new(GetGameVersion());
private static void _Load() private static void _Load()
{ {


+ 19
- 11
IPA.Injector/IPA.Injector.csproj View File

@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Common.props" /> <Import Project="..\Common.props" />
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net472</TargetFrameworks>
<TargetFramework>net472</TargetFramework>
<RootNamespace>IPA.Injector</RootNamespace> <RootNamespace>IPA.Injector</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>false</GenerateDocumentationFile> <GenerateDocumentationFile>false</GenerateDocumentationFile>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
@ -21,12 +21,12 @@
<PropertyGroup Condition="'$(BuildForBeatSaber)' == 'true'"> <PropertyGroup Condition="'$(BuildForBeatSaber)' == 'true'">
<DefineConstants>$(DefineConstants);BeatSaber</DefineConstants> <DefineConstants>$(DefineConstants);BeatSaber</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\IPA.Loader\IPA.Loader.csproj" /> <ProjectReference Include="..\IPA.Loader\IPA.Loader.csproj" />
<ProjectReference Include="..\Net3-Proxy\Net3-Proxy.csproj" Condition=" '$(TargetFramework)' == 'net35' " /> <ProjectReference Include="..\Net3-Proxy\Net3-Proxy.csproj" Condition=" '$(TargetFramework)' == 'net35' " />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="UnityEngine.CoreModule" Condition=" '$(TargetFramework)' == 'net472' "> <Reference Include="UnityEngine.CoreModule" Condition=" '$(TargetFramework)' == 'net472' ">
<HintPath>..\Refs\UnityEngine.CoreModule.Net4.dll</HintPath> <HintPath>..\Refs\UnityEngine.CoreModule.Net4.dll</HintPath>
@ -37,7 +37,7 @@
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net472' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
<Content Include="..\Libs\I18N.Net4.dll"> <Content Include="..\Libs\I18N.Net4.dll">
<Link>Libraries\Mono\I18N.dll</Link> <Link>Libraries\Mono\I18N.dll</Link>
@ -67,7 +67,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
<Content Include="..\Libs\I18N.Net3.dll"> <Content Include="..\Libs\I18N.Net3.dll">
<Link>Libraries\Mono\I18N.dll</Link> <Link>Libraries\Mono\I18N.dll</Link>
@ -82,14 +82,22 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.4" /> <PackageReference Include="Mono.Cecil" Version="0.11.4" />
<PackageReference Include="AsyncBridge" Version="0.3.1" /> <PackageReference Include="AsyncBridge" Version="0.3.1" />
<ProjectReference Include="..\SemVer\SemVer.csproj" /> <ProjectReference Include="..\SemVer\SemVer.csproj" />
</ItemGroup> </ItemGroup>
<Target Name="CopyDocumentation" BeforeTargets="Build">
<ItemGroup>
<ReferenceFiles Include="%(Reference.RelativeDir)%(Reference.Filename).xml" />
</ItemGroup>
<Message Text="Copying documentation" />
<Copy SourceFiles="@(ReferenceFiles)" DestinationFolder="$(OutputPath)Libs" Condition="Exists('%(RootDir)%(Directory)%(Filename)%(Extension)')" />
</Target>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Message Text="Relocating" Importance="normal" /> <Message Text="Relocating" Importance="normal" />
<ItemGroup> <ItemGroup>
@ -104,7 +112,7 @@
</ItemGroup> </ItemGroup>
<Move SourceFiles="@(SystemFiles)" DestinationFolder="$(OutputPath)Data\Managed" /> <Move SourceFiles="@(SystemFiles)" DestinationFolder="$(OutputPath)Data\Managed" />
<RemoveDir Directories="$(OutputPath)Libraries\Mono" /> <RemoveDir Directories="$(OutputPath)Libraries\Mono" />
<Delete Files="@(OldLibFiles)" /> <Delete Files="@(OldLibFiles)" />
<RemoveDir Directories="$(OutputPath)Libs" /> <RemoveDir Directories="$(OutputPath)Libs" />
<ItemGroup> <ItemGroup>


+ 28
- 31
IPA.Injector/Injector.cs View File

@ -45,8 +45,8 @@ namespace IPA.Injector
_ = args; _ = args;
try try
{ {
if (Environment.GetCommandLineArgs().Contains("--verbose"))
WinConsole.Initialize();
var arguments = Environment.GetCommandLineArgs();
MaybeInitializeConsole(arguments);
SetupLibraryLoading(); SetupLibraryLoading();
@ -55,8 +55,8 @@ namespace IPA.Injector
// this is weird, but it prevents Mono from having issues loading the type. // this is weird, but it prevents Mono from having issues loading the type.
// IMPORTANT: NO CALLS TO ANY LOGGER CAN HAPPEN BEFORE THIS // IMPORTANT: NO CALLS TO ANY LOGGER CAN HAPPEN BEFORE THIS
var unused = StandardLogger.PrintFilter; var unused = StandardLogger.PrintFilter;
#region // Above hack explaination
/*
#region // Above hack explanation
/*
* Due to an unknown bug in the version of Mono that Unity uses, if the first access to StandardLogger * Due to an unknown bug in the version of Mono that Unity uses, if the first access to StandardLogger
* is a call to a constructor, then Mono fails to load the type correctly. However, if the first access is to * is a call to a constructor, then Mono fails to load the type correctly. However, if the first access is to
* the above static property (or maybe any, but I don't really know) it behaves as expected and works fine. * the above static property (or maybe any, but I don't really know) it behaves as expected and works fine.
@ -65,7 +65,7 @@ namespace IPA.Injector
Default.Debug("Initializing logger"); Default.Debug("Initializing logger");
SelfConfig.ReadCommandLine(Environment.GetCommandLineArgs());
SelfConfig.ReadCommandLine(arguments);
SelfConfig.Load(); SelfConfig.Load();
DisabledConfig.Load(); DisabledConfig.Load();
@ -80,11 +80,13 @@ namespace IPA.Injector
Logging.Logger.Injector.Debug("Prepping bootstrapper"); Logging.Logger.Injector.Debug("Prepping bootstrapper");
// make sure to load the game version and check boundaries before installing the bootstrap, because that uses the game assemblies property
GameVersionEarly.Load();
SelfConfig.Instance.CheckVersionBoundary();
// updates backup // updates backup
InstallBootstrapPatch(); InstallBootstrapPatch();
GameVersionEarly.Load();
AntiMalwareEngine.Initialize(); AntiMalwareEngine.Initialize();
Updates.InstallPendingUpdates(); Updates.InstallPendingUpdates();
@ -100,6 +102,25 @@ namespace IPA.Injector
} }
} }
private static void MaybeInitializeConsole(string[] arguments)
{
var i = 0;
while (i < arguments.Length)
{
if (arguments[i++] == "--verbose")
{
if (i == arguments.Length)
{
WinConsole.Initialize(WinConsole.AttachParent);
return;
}
WinConsole.Initialize(int.TryParse(arguments[i], out int processId) ? processId : WinConsole.AttachParent);
return;
}
}
}
private static void EnsureDirectories() private static void EnsureDirectories()
{ {
string path; string path;
@ -116,11 +137,6 @@ namespace IPA.Injector
Loader.LibLoader.Configure(); Loader.LibLoader.Configure();
} }
private static void InstallHarmonyProtections()
{ // proxy function to delay resolution
HarmonyProtectorProxy.ProtectNull();
}
private static void InstallBootstrapPatch() private static void InstallBootstrapPatch()
{ {
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
@ -238,9 +254,6 @@ namespace IPA.Injector
endPatchCoreModule: endPatchCoreModule:
#endregion Insert patch into UnityEngine.CoreModule.dll #endregion Insert patch into UnityEngine.CoreModule.dll
Logging.Logger.Injector.Debug("Ensuring game assemblies are virtualized");
#region Virtualize game assemblies
bool isFirst = true; bool isFirst = true;
foreach (var name in SelfConfig.GameAssemblies_) foreach (var name in SelfConfig.GameAssemblies_)
{ {
@ -248,19 +261,6 @@ namespace IPA.Injector
using var execSec = CriticalSection.ExecuteSection(); using var execSec = CriticalSection.ExecuteSection();
try
{
Logging.Logger.Injector.Debug($"Virtualizing {name}");
using var ascModule = VirtualizedModule.Load(ascPath);
ascModule.Virtualize(cAsmName, () => bkp?.Add(ascPath));
}
catch (Exception e)
{
Logging.Logger.Injector.Error($"Could not virtualize {ascPath}");
if (SelfConfig.Debug_.ShowHandledErrorStackTraces_)
Logging.Logger.Injector.Error(e);
}
#if BeatSaber #if BeatSaber
if (isFirst) if (isFirst)
{ {
@ -292,7 +292,6 @@ namespace IPA.Injector
} }
#endif #endif
} }
#endregion
sw.Stop(); sw.Stop();
Logging.Logger.Injector.Info($"Installing bootstrapper took {sw.Elapsed}"); Logging.Logger.Injector.Info($"Installing bootstrapper took {sw.Elapsed}");
@ -317,8 +316,6 @@ namespace IPA.Injector
// need to reinit streams singe Unity seems to redirect stdout // need to reinit streams singe Unity seems to redirect stdout
StdoutInterceptor.RedirectConsole(); StdoutInterceptor.RedirectConsole();
InstallHarmonyProtections();
var bootstrapper = new GameObject("NonDestructiveBootstrapper").AddComponent<Bootstrapper>(); var bootstrapper = new GameObject("NonDestructiveBootstrapper").AddComponent<Bootstrapper>();
bootstrapper.Destroyed += Bootstrapper_Destroyed; bootstrapper.Destroyed += Bootstrapper_Destroyed;
} }


+ 0
- 219
IPA.Injector/Virtualizer.cs View File

@ -1,219 +0,0 @@
using Mono.Cecil;
using Mono.Cecil.Rocks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace IPA.Injector
{
internal class VirtualizedModule : IDisposable
{
private readonly FileInfo file;
private ModuleDefinition module;
public static VirtualizedModule Load(string engineFile)
{
return new VirtualizedModule(engineFile);
}
private VirtualizedModule(string assemblyFile)
{
file = new FileInfo(assemblyFile);
LoadModules();
}
private void LoadModules()
{
module = ModuleDefinition.ReadModule(file.FullName, new ReaderParameters
{
ReadWrite = false,
InMemory = true,
ReadingMode = ReadingMode.Immediate
});
}
public void Virtualize(AssemblyName selfName, Action beforeChangeCallback = null)
{
var changed = false;
var virtualize = true;
foreach (var r in module.AssemblyReferences)
{
if (r.Name == selfName.Name)
{
virtualize = false;
if (r.Version != selfName.Version)
{
r.Version = selfName.Version;
changed = true;
}
}
}
if (virtualize)
{
changed = true;
module.AssemblyReferences.Add(new AssemblyNameReference(selfName.Name, selfName.Version));
foreach (var type in module.Types)
{
VirtualizeType(type);
}
}
if (changed)
{
beforeChangeCallback?.Invoke();
module.Write(file.FullName);
}
}
private TypeReference inModreqRef;
// private TypeReference outModreqRef;
private void VirtualizeType(TypeDefinition type)
{
if(type.IsSealed)
{
// Unseal
type.IsSealed = false;
}
if (type.IsNestedPrivate)
{
type.IsNestedPrivate = false;
type.IsNestedPublic = true;
}
if (type.IsInterface) return;
if (type.IsAbstract) return;
// These two don't seem to work.
if (type.Name == "SceneControl" || type.Name == "ConfigUI") return;
// Take care of sub types
foreach (var subType in type.NestedTypes)
{
VirtualizeType(subType);
}
foreach (var method in type.Methods)
{
if (method.IsManaged
&& method.IsIL
&& !method.IsStatic
&& (!method.IsVirtual || method.IsFinal)
&& !method.IsAbstract
&& !method.IsAddOn
&& !method.IsConstructor
&& !method.IsSpecialName
&& !method.IsGenericInstance
&& !method.HasOverrides)
{
// fix In parameters to have the modreqs required by the compiler
foreach (var param in method.Parameters)
{
if (param.IsIn)
{
inModreqRef ??= module.ImportReference(typeof(System.Runtime.InteropServices.InAttribute));
param.ParameterType = AddModreqIfNotExist(param.ParameterType, inModreqRef);
}
// Breaks override methods if modreq is applied to `out` parameters
//if (param.IsOut)
//{
// outModreqRef ??= module.ImportReference(typeof(System.Runtime.InteropServices.OutAttribute));
// param.ParameterType = AddModreqIfNotExist(param.ParameterType, outModreqRef);
//}
}
method.IsVirtual = true;
method.IsFinal = false;
method.IsPublic = true;
method.IsPrivate = false;
method.IsNewSlot = true;
method.IsHideBySig = true;
}
}
foreach (var field in type.Fields)
{
if (field.IsPrivate) field.IsFamily = true;
}
}
private TypeReference AddModreqIfNotExist(TypeReference type, TypeReference mod)
{
var (element, opt, req) = GetDecomposedModifiers(type);
if (!req.Contains(mod))
{
req.Add(mod);
}
return BuildModifiedType(element, opt, req);
}
private (TypeReference Element, List<TypeReference> ModOpt, List<TypeReference> ModReq) GetDecomposedModifiers(TypeReference type)
{
var opt = new List<TypeReference>();
var req = new List<TypeReference>();
while (type is IModifierType modif)
{
if (type.IsOptionalModifier)
opt.Add(modif.ModifierType);
if (type.IsRequiredModifier)
req.Add(modif.ModifierType);
type = modif.ElementType;
}
return (type, opt, req);
}
private TypeReference BuildModifiedType(TypeReference type, IEnumerable<TypeReference> opt, IEnumerable<TypeReference> req)
{
foreach (var mod in req)
{
type = type.MakeRequiredModifierType(mod);
}
foreach (var mod in opt)
{
type = type.MakeOptionalModifierType(mod);
}
return type;
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
module.Dispose();
}
disposedValue = true;
}
}
~VirtualizedModule()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

+ 145
- 150
IPA.Loader/Config/Data/Value.cs View File

@ -1,151 +1,146 @@
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Config.Data
{
/// <summary>
/// A base value type for config data abstract representations, to be serialized with an
/// <see cref="IConfigProvider"/>. If a <see cref="Value"/> is <see langword="null"/>, then
/// that represents just that: a <c>null</c> in whatever serialization is being used.
/// Also contains factory functions for all derived types.
/// </summary>
public abstract class Value
{
/// <summary>
/// Converts this <see cref="Value"/> into a human-readable format.
/// </summary>
/// <returns>a human-readable string containing the value provided</returns>
public abstract override string ToString();
/// <summary>
/// Creates a Null <see cref="Value"/>.
/// </summary>
/// <returns><see langword="null"/></returns>
public static Value? Null() => null;
/// <summary>
/// Creates an empty <see cref="List"/>.
/// </summary>
/// <returns>an empty <see cref="List"/></returns>
/// <seealso cref="From(IEnumerable{Value})"/>
public static List List() => new();
/// <summary>
/// Creates an empty <see cref="Map"/>.
/// </summary>
/// <returns>an empty <see cref="Map"/></returns>
/// <seealso cref="From(IDictionary{string, Value})"/>
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map Map() => new();
/// <summary>
/// Creates a new <see cref="Value"/> representing a <see cref="string"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Text"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Text(string)"/>
[return: NotNullIfNotNull("val")]
public static Text? From(string? val) => Text(val);
/// <summary>
/// Creates a new <see cref="Data.Text"/> object wrapping a <see cref="string"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Text"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(string)"/>
[return: NotNullIfNotNull("val")]
public static Text? Text(string? val) => val == null ? null : new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="long"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Integer"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Integer(long)"/>
public static Integer From(long val) => Integer(val);
/// <summary>
/// Creates a new <see cref="Data.Integer"/> wrapping a <see cref="long"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Integer"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(long)"/>
public static Integer Integer(long val) => new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="double"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="FloatingPoint"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Float(decimal)"/>
public static FloatingPoint From(decimal val) => Float(val);
/// <summary>
/// Creates a new <see cref="FloatingPoint"/> wrapping a <see cref="decimal"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="FloatingPoint"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(decimal)"/>
public static FloatingPoint Float(decimal val) => new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="bool"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Boolean"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Bool(bool)"/>
public static Boolean From(bool val) => Bool(val);
/// <summary>
/// Creates a new <see cref="Boolean"/> wrapping a <see cref="bool"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Boolean"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(bool)"/>
public static Boolean Bool(bool val) => new(val);
/// <summary>
/// Creates a new <see cref="Data.List"/> holding the content of an <see cref="IEnumerable{T}"/>
/// of <see cref="Value"/>.
/// </summary>
/// <param name="vals">the <see cref="Value"/>s to initialize the <see cref="Data.List"/> with</param>
/// <returns>a <see cref="Data.List"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="List"/>
[return: NotNullIfNotNull("vals")]
public static List? From(IEnumerable<Value?>? vals)
{
if (vals is null) return null;
var l = List();
l.AddRange(vals);
return l;
}
/// <summary>
/// Creates a new <see cref="Data.Map"/> holding the content of an <see cref="IDictionary{TKey, TValue}"/>
/// of <see cref="string"/> to <see cref="Value"/>.
/// </summary>
/// <param name="vals">the dictionary of <see cref="Value"/>s to initialize the <see cref="Data.Map"/> wtih</param>
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/>
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map From(IDictionary<string, Value?> vals) => From(vals as IEnumerable<KeyValuePair<string, Value?>>);
/// <summary>
/// Creates a new <see cref="Data.Map"/> holding the content of an <see cref="IEnumerable{T}"/>
/// of <see cref="KeyValuePair{TKey, TValue}"/> of <see cref="string"/> to <see cref="Value"/>.
/// </summary>
/// <param name="vals">the enumerable of <see cref="KeyValuePair{TKey, TValue}"/> of name to <see cref="Value"/></param>
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/>
/// <seealso cref="From(IDictionary{string, Value})"/>
[return: NotNullIfNotNull("vals")]
public static Map? From(IEnumerable<KeyValuePair<string, Value?>>? vals)
{
if (vals is null) return null;
var m = Map();
foreach (var v in vals) m.Add(v.Key, v.Value);
return m;
}
}
}
namespace IPA.Config.Data
{
/// <summary>
/// A base value type for config data abstract representations, to be serialized with an
/// <see cref="IConfigProvider"/>. If a <see cref="Value"/> is <see langword="null"/>, then
/// that represents just that: a <c>null</c> in whatever serialization is being used.
/// Also contains factory functions for all derived types.
/// </summary>
public abstract class Value
{
/// <summary>
/// Converts this <see cref="Value"/> into a human-readable format.
/// </summary>
/// <returns>a human-readable string containing the value provided</returns>
public abstract override string ToString();
/// <summary>
/// Creates a Null <see cref="Value"/>.
/// </summary>
/// <returns><see langword="null"/></returns>
public static Value? Null() => null;
/// <summary>
/// Creates an empty <see cref="List"/>.
/// </summary>
/// <returns>an empty <see cref="List"/></returns>
/// <seealso cref="From(IEnumerable{Value})"/>
public static List List() => new();
/// <summary>
/// Creates an empty <see cref="Map"/>.
/// </summary>
/// <returns>an empty <see cref="Map"/></returns>
/// <seealso cref="From(IDictionary{string, Value})"/>
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map Map() => new();
/// <summary>
/// Creates a new <see cref="Value"/> representing a <see cref="string"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Text"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Text(string)"/>
[return: NotNullIfNotNull("val")]
public static Text? From(string? val) => Text(val);
/// <summary>
/// Creates a new <see cref="Data.Text"/> object wrapping a <see cref="string"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Text"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(string)"/>
[return: NotNullIfNotNull("val")]
public static Text? Text(string? val) => val == null ? null : new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="long"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Integer"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Integer(long)"/>
public static Integer From(long val) => Integer(val);
/// <summary>
/// Creates a new <see cref="Data.Integer"/> wrapping a <see cref="long"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Data.Integer"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(long)"/>
public static Integer Integer(long val) => new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="double"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="FloatingPoint"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Float(decimal)"/>
public static FloatingPoint From(decimal val) => Float(val);
/// <summary>
/// Creates a new <see cref="FloatingPoint"/> wrapping a <see cref="decimal"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="FloatingPoint"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(decimal)"/>
public static FloatingPoint Float(decimal val) => new(val);
/// <summary>
/// Creates a new <see cref="Value"/> wrapping a <see cref="bool"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Boolean"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="Bool(bool)"/>
public static Boolean From(bool val) => Bool(val);
/// <summary>
/// Creates a new <see cref="Boolean"/> wrapping a <see cref="bool"/>.
/// </summary>
/// <param name="val">the value to wrap</param>
/// <returns>a <see cref="Boolean"/> wrapping <paramref name="val"/></returns>
/// <seealso cref="From(bool)"/>
public static Boolean Bool(bool val) => new(val);
/// <summary>
/// Creates a new <see cref="Data.List"/> holding the content of an <see cref="IEnumerable{T}"/>
/// of <see cref="Value"/>.
/// </summary>
/// <param name="vals">the <see cref="Value"/>s to initialize the <see cref="Data.List"/> with</param>
/// <returns>a <see cref="Data.List"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="List"/>
[return: NotNullIfNotNull("vals")]
public static List? From(IEnumerable<Value?>? vals)
{
if (vals is null) return null;
var l = List();
l.AddRange(vals);
return l;
}
/// <summary>
/// Creates a new <see cref="Data.Map"/> holding the content of an <see cref="IDictionary{TKey, TValue}"/>
/// of <see cref="string"/> to <see cref="Value"/>.
/// </summary>
/// <param name="vals">the dictionary of <see cref="Value"/>s to initialize the <see cref="Data.Map"/> wtih</param>
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/>
/// <seealso cref="From(IEnumerable{KeyValuePair{string, Value}})"/>
public static Map From(IDictionary<string, Value?> vals) => From(vals as IEnumerable<KeyValuePair<string, Value?>>);
/// <summary>
/// Creates a new <see cref="Data.Map"/> holding the content of an <see cref="IEnumerable{T}"/>
/// of <see cref="KeyValuePair{TKey, TValue}"/> of <see cref="string"/> to <see cref="Value"/>.
/// </summary>
/// <param name="vals">the enumerable of <see cref="KeyValuePair{TKey, TValue}"/> of name to <see cref="Value"/></param>
/// <returns>a <see cref="Data.Map"/> containing the content of <paramref name="vals"/></returns>
/// <seealso cref="Map"/>
/// <seealso cref="From(IDictionary{string, Value})"/>
[return: NotNullIfNotNull("vals")]
public static Map? From(IEnumerable<KeyValuePair<string, Value?>>? vals)
{
if (vals is null) return null;
var m = Map();
foreach (var v in vals) m.Add(v.Key, v.Value);
return m;
}
}
}

+ 40
- 7
IPA.Loader/Config/SelfConfig.cs View File

@ -53,6 +53,12 @@ namespace IPA.Config
case "--no-yeet": case "--no-yeet":
CommandLineValues.YeetMods = false; CommandLineValues.YeetMods = false;
break; break;
case "--no-logs":
CommandLineValues.WriteLogs = false;
break;
case "--darken-message":
CommandLineValues.Debug.DarkenMessages = true;
break;
case "--condense-logs": case "--condense-logs":
CommandLineValues.Debug.CondenseModLogs = true; CommandLineValues.Debug.CondenseModLogs = true;
break; break;
@ -72,10 +78,18 @@ namespace IPA.Config
} }
} }
public void CheckVersionBoundary()
{
if (ResetGameAssebliesOnVersionChange && Utilities.UnityGame.IsGameVersionBoundary)
{
GameAssemblies = GetDefaultGameAssemblies();
}
}
internal const string IPAName = "Beat Saber IPA"; internal const string IPAName = "Beat Saber IPA";
internal const string IPAVersion = "4.2.2.0";
internal const string IPAVersion = "4.3.0.0";
// uses Updates.AutoUpdate, Updates.AutoCheckUpdates, YeetMods, Debug.ShowCallSource, Debug.ShowDebug,
// uses Updates.AutoUpdate, Updates.AutoCheckUpdates, YeetMods, Debug.ShowCallSource, Debug.ShowDebug,
// Debug.CondenseModLogs // Debug.CondenseModLogs
internal static SelfConfig CommandLineValues = new(); internal static SelfConfig CommandLineValues = new();
@ -148,6 +162,11 @@ namespace IPA.Config
public virtual bool SyncLogging { get; set; } = false; public virtual bool SyncLogging { get; set; } = false;
// LINE: ignore // LINE: ignore
public static bool SyncLogging_ => Instance?.Debug?.SyncLogging ?? false; public static bool SyncLogging_ => Instance?.Debug?.SyncLogging ?? false;
public virtual bool DarkenMessages { get; set; } = false;
// LINE: ignore 2
public static bool DarkenMessages_ => (Instance?.Debug?.DarkenMessages ?? false)
|| CommandLineValues.Debug.DarkenMessages;
} }
// LINE: ignore // LINE: ignore
@ -174,19 +193,33 @@ namespace IPA.Config
public static bool YeetMods_ => (Instance?.YeetMods ?? true) public static bool YeetMods_ => (Instance?.YeetMods ?? true)
&& CommandLineValues.YeetMods; && CommandLineValues.YeetMods;
[JsonIgnore]
public bool WriteLogs { get; set; } = true;
public virtual bool ResetGameAssebliesOnVersionChange { get; set; } = true;
// LINE: ignore // LINE: ignore
[NonNullable, UseConverter(typeof(CollectionConverter<string, HashSet<string?>>))] [NonNullable, UseConverter(typeof(CollectionConverter<string, HashSet<string?>>))]
public virtual HashSet<string> GameAssemblies { get; set; } = new HashSet<string>
public virtual HashSet<string> GameAssemblies { get; set; } = GetDefaultGameAssemblies();
// BEGIN: section ignore
public static HashSet<string> GetDefaultGameAssemblies()
=> new()
{ {
// LINE: ignore 5
#if BeatSaber // provide these defaults only for Beat Saber builds #if BeatSaber // provide these defaults only for Beat Saber builds
"Main.dll", "Core.dll", "HMLib.dll", "HMUI.dll", "HMRendering.dll", "VRUI.dll",
"BeatmapCore.dll", "GameplayCore.dll","HMLibAttributes.dll",
"Main.dll", "Core.dll", "HMLib.dll", "HMUI.dll", "HMRendering.dll", "VRUI.dll",
"BeatmapCore.dll", "GameplayCore.dll", "HMLibAttributes.dll", "BeatmapEditor3D.dll"
#else // otherwise specify Assembly-CSharp.dll #else // otherwise specify Assembly-CSharp.dll
"Assembly-CSharp.dll" "Assembly-CSharp.dll"
// LINE: ignore
#endif #endif
}; };
// END: section ignore
// LINE: ignore
#if false // used to make schema gen happy
private static HashSet<string> GetDefaultGameAssemblies() => null;
// LINE: ignore
#endif
// LINE: ignore // LINE: ignore
public static HashSet<string> GameAssemblies_ => Instance?.GameAssemblies ?? new HashSet<string> { "Assembly-CSharp.dll" }; public static HashSet<string> GameAssemblies_ => Instance?.GameAssemblies ?? new HashSet<string> { "Assembly-CSharp.dll" };


+ 1
- 1
IPA.Loader/Config/Stores/Converters.cs View File

@ -80,7 +80,7 @@ namespace IPA.Config.Stores.Converters
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
{ // this is a Nullable { // this is a Nullable
//Logger.log.Debug($"gives NullableConverter<{Nullable.GetUnderlyingType(t)}>"); //Logger.log.Debug($"gives NullableConverter<{Nullable.GetUnderlyingType(t)}>");
return (typeof(NullableConverter<>).MakeGenericType(Nullable.GetUnderlyingType(t)));
return typeof(NullableConverter<>).MakeGenericType(Nullable.GetUnderlyingType(t));
} }
//Logger.log.Debug($"gives converter for value type {t}"); //Logger.log.Debug($"gives converter for value type {t}");


+ 1
- 0
IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs View File

@ -46,6 +46,7 @@ namespace IPA.Config.Stores
var endLabel = il.DefineLabel(); var endLabel = il.DefineLabel();
// TODO: when we have a nullable, we need to save to a local to call methods
if (member.IsNullable) if (member.IsNullable)
{ {
il.Emit(OpCodes.Dup); il.Emit(OpCodes.Dup);


+ 1
- 0
IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs View File

@ -112,6 +112,7 @@ namespace IPA.Config.Stores
{ {
il.Emit(OpCodes.Stloc, mapLocal); il.Emit(OpCodes.Stloc, mapLocal);
// TODO: handle creating a nullable, when applicable
EmitLoad(il, member, thisarg); EmitLoad(il, member, thisarg);
il.Emit(OpCodes.Stloc, resultLocal); il.Emit(OpCodes.Stloc, resultLocal);


+ 1
- 1
IPA.Loader/IPA.Loader.csproj View File

@ -50,7 +50,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Ionic.Zip" Version="1.9.1.8" /> <PackageReference Include="Ionic.Zip" Version="1.9.1.8" />
<!--<PackageReference Include="Lib.Harmony" Version="2.0.2" />--> <!--<PackageReference Include="Lib.Harmony" Version="2.0.2" />-->
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<!--<PackageReference Include="AsyncBridge" Version="0.3.1" /> <!--<PackageReference Include="AsyncBridge" Version="0.3.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0"> <PackageReference Include="System.ValueTuple" Version="4.5.0">
<ExcludeAssets>buildtransitive</ExcludeAssets> <ExcludeAssets>buildtransitive</ExcludeAssets>


+ 0
- 44
IPA.Loader/Loader/HarmonyProtector.cs View File

@ -1,44 +0,0 @@
using HarmonyLib;
using System.Reflection;
namespace IPA.Loader
{
internal static class HarmonyProtectorProxy
{
public static void ProtectNull() => HarmonyProtector.Protect();
}
internal static class HarmonyProtector
{
private static Harmony instance;
private static Assembly selfAssem;
private static Assembly harmonyAssem;
public static void Protect(Harmony inst = null)
{
selfAssem = Assembly.GetExecutingAssembly();
harmonyAssem = typeof(Harmony).Assembly;
if (inst == null)
{
if (instance == null)
instance = new Harmony("BSIPA Safeguard");
inst = instance;
}
var target = typeof(PatchProcessor).GetMethod("Patch");
var patch = typeof(HarmonyProtector).GetMethod(nameof(PatchProcessor_Patch_Prefix), BindingFlags.NonPublic | BindingFlags.Static);
inst.Patch(target, new HarmonyMethod(patch));
}
private static bool PatchProcessor_Patch_Prefix(MethodBase ___original, out MethodInfo __result)
{
var asm = ___original.DeclaringType.Assembly;
__result = ___original as MethodInfo;
return !(asm.Equals(selfAssem) || asm.Equals(harmonyAssem));
}
}
}

+ 13
- 4
IPA.Loader/Loader/PluginLoader.cs View File

@ -104,7 +104,9 @@ namespace IPA.Loader
throw new InvalidOperationException())) throw new InvalidOperationException()))
manifest = manifestReader.ReadToEnd(); manifest = manifestReader.ReadToEnd();
selfMeta.Manifest = JsonConvert.DeserializeObject<PluginManifest>(manifest) ?? throw new NotSupportedException();
var manifestObj = JsonConvert.DeserializeObject<PluginManifest>(manifest);
selfMeta.Manifest = manifestObj ?? throw new InvalidOperationException("Deserialized manifest was null");
PluginsMetadata.Add(selfMeta); PluginsMetadata.Add(selfMeta);
SelfMeta = selfMeta; SelfMeta = selfMeta;
@ -288,10 +290,17 @@ namespace IPA.Loader
IsSelf = false, IsSelf = false,
IsBare = true, IsBare = true,
}; };
metadata.Manifest = JsonConvert.DeserializeObject<PluginManifest>(File.ReadAllText(manifest)) ?? throw new NotSupportedException();
var manifestRelative = manifest.Replace(UnityGame.InstallPath, "").TrimStart(Path.DirectorySeparatorChar); var manifestRelative = manifest.Replace(UnityGame.InstallPath, "").TrimStart(Path.DirectorySeparatorChar);
var manifestObj = JsonConvert.DeserializeObject<PluginManifest>(File.ReadAllText(manifest));
if (manifestObj is null)
{
Logger.Loader.Error($"Bare manifest {manifestRelative} deserialized to null");
continue;
}
metadata.Manifest = manifestObj;
if (metadata.Manifest.Files.Length < 1) if (metadata.Manifest.Files.Length < 1)
Logger.Loader.Warn($"Bare manifest {manifestRelative} does not declare any files. " + Logger.Loader.Warn($"Bare manifest {manifestRelative} does not declare any files. " +


+ 4
- 2
IPA.Loader/Loader/PluginManager.cs View File

@ -199,7 +199,8 @@ namespace IPA.Loader
var res = TaskEx.WhenAll(exec.Dependents.Select(d => Disable(d, alreadyDisabled))) var res = TaskEx.WhenAll(exec.Dependents.Select(d => Disable(d, alreadyDisabled)))
.ContinueWith(t => .ContinueWith(t =>
{ {
if (t.IsFaulted) {
if (t.IsFaulted)
{
return TaskEx.WhenAll(t, TaskEx6.FromException( return TaskEx.WhenAll(t, TaskEx6.FromException(
new CannotRuntimeDisableException(exec.Executor.Metadata, "Dependents cannot be disabled for plugin"))); new CannotRuntimeDisableException(exec.Executor.Metadata, "Dependents cannot be disabled for plugin")));
} }
@ -208,7 +209,8 @@ namespace IPA.Loader
{ {
foreach (var feature in exec.Executor.Metadata.Features) foreach (var feature in exec.Executor.Metadata.Features)
{ {
try {
try
{
feature.AfterDisable(exec.Executor.Metadata); feature.AfterDisable(exec.Executor.Metadata);
} }
catch (Exception e) catch (Exception e)


+ 1
- 1
IPA.Loader/Loader/manifest.json View File

@ -7,7 +7,7 @@
], ],
"id": "BSIPA", "id": "BSIPA",
"name": "Beat Saber IPA", "name": "Beat Saber IPA",
"version": "4.2.2",
"version": "4.3.0",
"icon": "IPA.icon_white.png", "icon": "IPA.icon_white.png",
"features": { "features": {
"IPA.DefineFeature": [ "IPA.DefineFeature": [


+ 31
- 53
IPA.Loader/Logging/ConsoleWindow.cs View File

@ -22,14 +22,12 @@ namespace IPA.Logging
internal static bool IsInitialized; internal static bool IsInitialized;
public static void Initialize(bool alwaysCreateNewConsole = false)
public static void Initialize(int processId, bool alwaysCreateNewConsole = false)
{ {
bool consoleAttached = true;
if (alwaysCreateNewConsole
|| (AttachConsole(AttachParent) == 0
&& Marshal.GetLastWin32Error() != ErrorAccessDenied))
bool consoleAttached;
if (alwaysCreateNewConsole || !(consoleAttached = AttachConsole(processId)))
{ {
consoleAttached = AllocConsole() != 0;
consoleAttached = AllocConsole();
} }
if (consoleAttached) if (consoleAttached)
@ -39,7 +37,7 @@ namespace IPA.Logging
} }
} }
public static void InitializeStreams()
private static void InitializeStreams()
{ {
InitializeOutStream(); InitializeOutStream();
InitializeInStream(); InitializeInStream();
@ -62,7 +60,7 @@ namespace IPA.Logging
if (!SetConsoleMode(handle, mode)) if (!SetConsoleMode(handle, mode))
{ {
UseVTEscapes = false; UseVTEscapes = false;
Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " +
Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " +
new Win32Exception(Marshal.GetLastWin32Error()).Message); new Win32Exception(Marshal.GetLastWin32Error()).Message);
} }
} }
@ -87,18 +85,11 @@ namespace IPA.Logging
private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode, private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
FileAccess dotNetFileAccess, out SafeFileHandle handle) FileAccess dotNetFileAccess, out SafeFileHandle handle)
{ {
var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero), true);
var file = new SafeFileHandle(CreateFile(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero), true);
if (!file.IsInvalid) if (!file.IsInvalid)
{ {
handle = file; handle = file;
#if NET4
var fs = new FileStream(file, dotNetFileAccess); var fs = new FileStream(file, dotNetFileAccess);
#elif NET3
#pragma warning disable CS0618
// this is marked obsolete, and shouldn't need to be used, but the constructor used in .NET 4 doesn't exist in Unity's mscorlib.dll
var fs = new FileStream(file.DangerousGetHandle(), dotNetFileAccess);
#pragma warning restore
#endif
return fs; return fs;
} }
@ -106,43 +97,31 @@ namespace IPA.Logging
return null; return null;
} }
#region Win API Functions and Constants
[DllImport("kernel32.dll",
EntryPoint = "AllocConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();
[DllImport("kernel32.dll",
EntryPoint = "AttachConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern uint AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll",
EntryPoint = "CreateFileW",
SetLastError = true,
CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr CreateFileW(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
#region Win API Functions and Constants
[DllImport("kernel32.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode,
IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[DllImport("kernel32.dll", SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[DllImport("kernel32.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern IntPtr GetStdHandle(int nStdHandle); private static extern IntPtr GetStdHandle(int nStdHandle);
private const uint EnableVTProcessing = 0x0004; private const uint EnableVTProcessing = 0x0004;
@ -153,10 +132,9 @@ namespace IPA.Logging
private const uint FileShareWrite = 0x00000002; private const uint FileShareWrite = 0x00000002;
private const uint OpenExisting = 0x00000003; private const uint OpenExisting = 0x00000003;
private const uint FileAttributeNormal = 0x80; private const uint FileAttributeNormal = 0x80;
private const uint ErrorAccessDenied = 5;
private const uint AttachParent = 0xFFFFFFFF;
#endregion
internal const int AttachParent = -1;
#endregion
} }
} }

+ 39
- 38
IPA.Loader/Logging/LogPrinter.cs View File

@ -1,39 +1,40 @@
using System;
namespace IPA.Logging
{
/// <summary>
/// The log printer's base class.
/// </summary>
public abstract class LogPrinter
{
/// <summary>
/// Provides a filter for which log levels to allow through.
/// </summary>
/// <value>the level to filter to</value>
public abstract Logger.LogLevel Filter { get; set; }
/// <summary>
/// Prints a provided message from a given log at the specified time.
/// </summary>
/// <param name="level">the log level</param>
/// <param name="time">the time the message was composed</param>
/// <param name="logName">the name of the log that created this message</param>
/// <param name="message">the message</param>
public abstract void Print(Logger.Level level, DateTime time, string logName, string message);
/// <summary>
/// Called before the first print in a group. May be called multiple times.
/// Use this to create file handles and the like.
/// </summary>
public virtual void StartPrint() { }
/// <summary>
/// Called after the last print in a group. May be called multiple times.
/// Use this to dispose file handles and the like.
/// </summary>
public virtual void EndPrint() { }
internal DateTime LastUse { get; set; }
}
#nullable enable
using System;
namespace IPA.Logging
{
/// <summary>
/// The log printer's base class.
/// </summary>
public abstract class LogPrinter
{
/// <summary>
/// Provides a filter for which log levels to allow through.
/// </summary>
/// <value>the level to filter to</value>
public abstract Logger.LogLevel Filter { get; set; }
/// <summary>
/// Prints a provided message from a given log at the specified time.
/// </summary>
/// <param name="level">the log level</param>
/// <param name="time">the time the message was composed</param>
/// <param name="logName">the name of the log that created this message</param>
/// <param name="message">the message</param>
public abstract void Print(Logger.Level level, DateTime time, string logName, string message);
/// <summary>
/// Called before the first print in a group. May be called multiple times.
/// Use this to create file handles and the like.
/// </summary>
public virtual void StartPrint() { }
/// <summary>
/// Called after the last print in a group. May be called multiple times.
/// Use this to dispose file handles and the like.
/// </summary>
public virtual void EndPrint() { }
internal DateTime LastUse { get; set; }
}
} }

+ 159
- 116
IPA.Loader/Logging/Printers/ColoredConsolePrinter.cs View File

@ -1,116 +1,159 @@
using System;
using System.Runtime.InteropServices;
namespace IPA.Logging.Printers
{
/// <summary>
/// Prints a pretty message to the console.
/// </summary>
public class ColoredConsolePrinter : LogPrinter
{
private Logger.LogLevel filter = Logger.LogLevel.All;
/// <summary>
/// A filter for this specific printer.
/// </summary>
/// <value>the filter to apply to this printer</value>
public override Logger.LogLevel Filter { get => filter; set => filter = value; }
/// <summary>
/// The color to print messages as.
/// </summary>
/// <value>the color to print this message as</value>
// Initializer calls this function because Unity's .NET 3.5 doesn't have the color properties on Console
public ConsoleColor Color { get; set; } = GetConsoleColor(WinConsole.OutHandle);
/// <summary>
/// Prints an entry to the console window.
/// </summary>
/// <param name="level">the <see cref="Logger.Level"/> of the message</param>
/// <param name="time">the <see cref="DateTime"/> the message was recorded at</param>
/// <param name="logName">the name of the log that sent the message</param>
/// <param name="message">the message to print</param>
public override void Print(Logger.Level level, DateTime time, string logName, string message)
{
if (((byte)level & (byte)StandardLogger.PrintFilter) == 0) return;
EnsureDefaultsPopulated(WinConsole.OutHandle);
SetColor(Color, WinConsole.OutHandle);
foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
WinConsole.ConOut.WriteLine(Logger.LogFormat, line, logName, time, level.ToString().ToUpper());
ResetColor(WinConsole.OutHandle);
}
private static bool _haveReadDefaultColors;
private static short _defaultColors;
private void EnsureDefaultsPopulated(IntPtr handle, bool force = false)
{
if (!_haveReadDefaultColors | force)
{
GetConsoleScreenBufferInfo(handle, out var info);
_defaultColors = (short)(info.Attribute & ~15);
_haveReadDefaultColors = true;
}
}
private void ResetColor(IntPtr handle)
{
GetConsoleScreenBufferInfo(handle, out var info);
var otherAttrs = (short)(info.Attribute & ~15);
SetConsoleTextAttribute(handle, (short)(otherAttrs | _defaultColors));
}
private void SetColor(ConsoleColor col, IntPtr handle)
{
GetConsoleScreenBufferInfo(handle, out var info);
var attr = GetAttrForeground(info.Attribute, col);
SetConsoleTextAttribute(handle, attr);
}
private static short GetAttrForeground(int attr, ConsoleColor color)
{
attr &= ~15;
return (short)(attr | (int)color);
}
private static ConsoleColor GetConsoleColor(IntPtr handle)
{
GetConsoleScreenBufferInfo(handle, out var info);
return (ConsoleColor)(info.Attribute & 15);
}
// ReSharper disable NotAccessedField.Local
#pragma warning disable 649
private struct Coordinate
{
public short X;
public short Y;
}
private struct SmallRect
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
private struct ConsoleScreenBufferInfo
{
public Coordinate Size;
public Coordinate CursorPosition;
public short Attribute;
public SmallRect Window;
public Coordinate MaxWindowSize;
}
#pragma warning restore 649
// ReSharper restore NotAccessedField.Local
[DllImport("kernel32.dll", EntryPoint = "GetConsoleScreenBufferInfo", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool GetConsoleScreenBufferInfo(IntPtr handle, out ConsoleScreenBufferInfo info);
[DllImport("kernel32.dll", EntryPoint = "SetConsoleTextAttribute", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool SetConsoleTextAttribute(IntPtr handle, short attribute);
}
}
#nullable enable
using System;
using System.Runtime.InteropServices;
namespace IPA.Logging.Printers
{
/// <summary>
/// Prints a pretty message to the console.
/// </summary>
public class ColoredConsolePrinter : LogPrinter
{
private Logger.LogLevel filter = Logger.LogLevel.All;
/// <summary>
/// A filter for this specific printer.
/// </summary>
/// <value>the filter to apply to this printer</value>
public override Logger.LogLevel Filter { get => filter; set => filter = value; }
/// <summary>
/// The color to print messages as.
/// </summary>
/// <value>the color to print this message as</value>
// Initializer calls this function because Unity's .NET 3.5 doesn't have the color properties on Console
public ConsoleColor Color { get; set; } = GetConsoleColor(WinConsole.OutHandle);
private static ConsoleColor GetDarkenedColor(ConsoleColor color)
=> color switch
{
ConsoleColor.Gray => ConsoleColor.DarkGray,
ConsoleColor.Blue => ConsoleColor.DarkBlue,
ConsoleColor.Green => ConsoleColor.DarkGreen,
ConsoleColor.Cyan => ConsoleColor.DarkCyan,
ConsoleColor.Red => ConsoleColor.DarkRed,
ConsoleColor.Magenta => ConsoleColor.DarkMagenta,
ConsoleColor.Yellow => ConsoleColor.DarkYellow,
ConsoleColor.White => ConsoleColor.Gray,
_ => color,
};
private readonly bool darkenSetManually;
private readonly bool darkenMessages;
public ColoredConsolePrinter() : this(Config.SelfConfig.Debug_.DarkenMessages_)
{
darkenSetManually = false;
}
public ColoredConsolePrinter(bool darkenMessages)
{
darkenSetManually = true;
this.darkenMessages = darkenMessages;
}
/// <summary>
/// Prints an entry to the console window.
/// </summary>
/// <param name="level">the <see cref="Logger.Level"/> of the message</param>
/// <param name="time">the <see cref="DateTime"/> the message was recorded at</param>
/// <param name="logName">the name of the log that sent the message</param>
/// <param name="message">the message to print</param>
public override void Print(Logger.Level level, DateTime time, string logName, string message)
{
if (((byte)level & (byte)StandardLogger.PrintFilter) == 0) return;
EnsureDefaultsPopulated(WinConsole.OutHandle);
SetColor(Color, WinConsole.OutHandle);
var prefixStr = "";
var suffixStr = "";
if ((darkenSetManually && darkenMessages) || (!darkenSetManually && Config.SelfConfig.Debug_.DarkenMessages_))
{
var darkened = GetDarkenedColor(Color);
if (darkened != Color)
{
prefixStr = StdoutInterceptor.ConsoleColorToForegroundSet(darkened);
suffixStr = StdoutInterceptor.ConsoleColorToForegroundSet(Color);
}
}
foreach (var line in message.Split(new[] { "\n", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
WinConsole.ConOut.WriteLine(Logger.LogFormat, prefixStr + line + suffixStr, logName, time, level.ToString().ToUpperInvariant());
ResetColor(WinConsole.OutHandle);
}
private static bool _haveReadDefaultColors;
private static short _defaultColors;
private static void EnsureDefaultsPopulated(IntPtr handle, bool force = false)
{
if (!_haveReadDefaultColors | force)
{
_ = GetConsoleScreenBufferInfo(handle, out var info);
_defaultColors = (short)(info.Attribute & ~15);
_haveReadDefaultColors = true;
}
}
private static void ResetColor(IntPtr handle)
{
_ = GetConsoleScreenBufferInfo(handle, out var info);
var otherAttrs = (short)(info.Attribute & ~15);
_ = SetConsoleTextAttribute(handle, (short)(otherAttrs | _defaultColors));
}
private static void SetColor(ConsoleColor col, IntPtr handle)
{
_ = GetConsoleScreenBufferInfo(handle, out var info);
var attr = GetAttrForeground(info.Attribute, col);
_ = SetConsoleTextAttribute(handle, attr);
}
private static short GetAttrForeground(int attr, ConsoleColor color)
{
attr &= ~15;
return (short)(attr | (int)color);
}
private static ConsoleColor GetConsoleColor(IntPtr handle)
{
_ = GetConsoleScreenBufferInfo(handle, out var info);
return (ConsoleColor)(info.Attribute & 15);
}
// ReSharper disable NotAccessedField.Local
#pragma warning disable 649
private struct Coordinate
{
public short X;
public short Y;
}
private struct SmallRect
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
private struct ConsoleScreenBufferInfo
{
public Coordinate Size;
public Coordinate CursorPosition;
public short Attribute;
public SmallRect Window;
public Coordinate MaxWindowSize;
}
#pragma warning restore 649
// ReSharper restore NotAccessedField.Local
[DllImport("kernel32.dll", EntryPoint = "GetConsoleScreenBufferInfo", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool GetConsoleScreenBufferInfo(IntPtr handle, out ConsoleScreenBufferInfo info);
[DllImport("kernel32.dll", EntryPoint = "SetConsoleTextAttribute", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool SetConsoleTextAttribute(IntPtr handle, short attribute);
}
}

+ 34
- 22
IPA.Loader/Logging/Printers/GZFilePrinter.cs View File

@ -1,5 +1,7 @@
using Ionic.Zlib;
#nullable enable
using Ionic.Zlib;
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -29,17 +31,17 @@ namespace IPA.Logging.Printers
private const RegexOptions reOptions = RegexOptions.None; private const RegexOptions reOptions = RegexOptions.None;
#endif #endif
internal static Regex removeControlCodes = new Regex("\x1b\\[\\d+m", reOptions);
internal static Regex removeControlCodes = new("\x1b\\[\\d+m", reOptions);
private FileInfo fileInfo;
private FileInfo? fileInfo;
/// <summary> /// <summary>
/// The <see cref="StreamWriter"/> that writes to the GZip file. /// The <see cref="StreamWriter"/> that writes to the GZip file.
/// </summary> /// </summary>
/// <value>the writer to the underlying filestream</value> /// <value>the writer to the underlying filestream</value>
protected StreamWriter FileWriter;
protected StreamWriter? FileWriter;
private FileStream fstream;
private FileStream? fstream;
/// <summary> /// <summary>
/// Gets the <see cref="FileInfo"/> for the file to write to. /// Gets the <see cref="FileInfo"/> for the file to write to.
@ -49,6 +51,7 @@ namespace IPA.Logging.Printers
private const string latestFormat = "_latest{0}"; private const string latestFormat = "_latest{0}";
[MemberNotNull(nameof(fileInfo))]
private void InitLog() private void InitLog()
{ {
try try
@ -90,21 +93,30 @@ namespace IPA.Logging.Printers
{ {
Logger.Default.Error("Error initializing log!"); Logger.Default.Error("Error initializing log!");
Logger.Default.Error(e); Logger.Default.Error(e);
throw;
} }
} }
private static async void CompressOldLog(FileInfo file) private static async void CompressOldLog(FileInfo file)
{ {
Logger.Default.Debug($"Compressing log file {file}");
try
{
Logger.Default.Debug($"Compressing log file {file}");
var newFile = new FileInfo(file.FullName + ".gz");
var newFile = new FileInfo(file.FullName + ".gz");
using (var istream = file.OpenRead())
using (var ostream = newFile.Create())
using (var gz = new GZipStream(ostream, CompressionMode.Compress, CompressionLevel.BestCompression, false))
await istream.CopyToAsync(gz);
using (var istream = file.OpenRead())
using (var ostream = newFile.Create())
using (var gz = new GZipStream(ostream, CompressionMode.Compress, CompressionLevel.BestCompression, false))
await istream.CopyToAsync(gz).ConfigureAwait(false);
file.Delete();
file.Delete();
}
catch (Exception e)
{
Logger.Default.Error("Error compressing old log file:");
Logger.Default.Error(e);
}
} }
/// <summary> /// <summary>
@ -123,10 +135,10 @@ namespace IPA.Logging.Printers
/// </summary> /// </summary>
public sealed override void EndPrint() public sealed override void EndPrint()
{ {
FileWriter.Flush();
fstream.Flush();
FileWriter.Dispose();
fstream.Dispose();
FileWriter?.Flush();
fstream?.Flush();
FileWriter?.Dispose();
fstream?.Dispose();
FileWriter = null; FileWriter = null;
fstream = null; fstream = null;
} }
@ -146,12 +158,12 @@ namespace IPA.Logging.Printers
{ {
if (disposing) if (disposing)
{ {
FileWriter.Flush();
fstream.Flush();
FileWriter.Close();
fstream.Close();
FileWriter.Dispose();
fstream.Dispose();
FileWriter?.Flush();
fstream?.Flush();
FileWriter?.Close();
fstream?.Close();
FileWriter?.Dispose();
fstream?.Dispose();
} }
} }
} }

+ 8
- 5
IPA.Loader/Logging/StandardLogger.cs View File

@ -25,10 +25,7 @@ namespace IPA.Logging
/// </remarks> /// </remarks>
public class StandardLogger : Logger public class StandardLogger : Logger
{ {
private static readonly List<LogPrinter> defaultPrinters = new()
{
new GlobalLogFilePrinter()
};
private static readonly List<LogPrinter> defaultPrinters = new();
static StandardLogger() static StandardLogger()
{ {
@ -115,6 +112,7 @@ namespace IPA.Logging
private readonly Dictionary<string, StandardLogger> children = new(); private readonly Dictionary<string, StandardLogger> children = new();
private static bool addedFilePrinter = false;
/// <summary> /// <summary>
/// Configures internal debug settings based on the config passed in. /// Configures internal debug settings based on the config passed in.
/// </summary> /// </summary>
@ -124,6 +122,11 @@ namespace IPA.Logging
PrintFilter = SelfConfig.Debug_.ShowDebug_ ? LogLevel.All : LogLevel.InfoUp; PrintFilter = SelfConfig.Debug_.ShowDebug_ ? LogLevel.All : LogLevel.InfoUp;
showTrace = SelfConfig.Debug_.ShowTrace_; showTrace = SelfConfig.Debug_.ShowTrace_;
syncLogging = SelfConfig.Debug_.SyncLogging_; syncLogging = SelfConfig.Debug_.SyncLogging_;
if (SelfConfig.CommandLineValues.WriteLogs && !addedFilePrinter)
{
addedFilePrinter = true;
AddDefaultPrinter(new GlobalLogFilePrinter());
}
} }
private StandardLogger(StandardLogger parent, string subName) private StandardLogger(StandardLogger parent, string subName)
@ -457,4 +460,4 @@ namespace IPA.Logging
_ => throw new InvalidOperationException() _ => throw new InvalidOperationException()
}; };
} }
}
}

+ 1
- 1
IPA.Loader/Logging/StdoutInterceptor.cs View File

@ -54,7 +54,7 @@ namespace IPA.Logging
private const ConsoleColor defaultColor = ConsoleColor.Gray; private const ConsoleColor defaultColor = ConsoleColor.Gray;
private ConsoleColor currentColor = defaultColor; private ConsoleColor currentColor = defaultColor;
private static string ConsoleColorToForegroundSet(ConsoleColor col)
internal static string ConsoleColorToForegroundSet(ConsoleColor col)
{ {
if (!WinConsole.UseVTEscapes) return ""; if (!WinConsole.UseVTEscapes) return "";
string code = "0"; // reset string code = "0"; // reset


+ 1
- 1
IPA.Loader/Utilities/AlmostVersion.cs View File

@ -190,7 +190,7 @@ namespace IPA.Utilities
/// <param name="obj">the object to compare to</param> /// <param name="obj">the object to compare to</param>
/// <returns><see langword="true"/> if they are equal, <see langword="false"/> otherwise</returns> /// <returns><see langword="true"/> if they are equal, <see langword="false"/> otherwise</returns>
/// <seealso cref="object.Equals(object)"/> /// <seealso cref="object.Equals(object)"/>
public override bool Equals(object obj)
public override bool Equals(object? obj)
{ {
return obj is AlmostVersion version && return obj is AlmostVersion version &&
SemverValue == version.SemverValue && SemverValue == version.SemverValue &&


+ 4
- 4
IPA.Loader/Utilities/Async/Coroutines.cs View File

@ -54,10 +54,10 @@ namespace IPA.Utilities.Async
public static Task AsTask(IEnumerator coroutine) public static Task AsTask(IEnumerator coroutine)
{ {
if (!UnityGame.OnMainThread) if (!UnityGame.OnMainThread)
return UnityMainThreadTaskScheduler.Factory.StartNew(() => AsTask(coroutine)).Unwrap();
return UnityMainThreadTaskScheduler.Factory.StartNew(() => AsTask(coroutine), default, default, UnityMainThreadTaskScheduler.Default).Unwrap();
var tcs = new TaskCompletionSource<VoidStruct>(coroutine, AsTaskSourceOptions); var tcs = new TaskCompletionSource<VoidStruct>(coroutine, AsTaskSourceOptions);
PluginComponent.Instance.StartCoroutine(new AsTaskCoroutineExecutor(coroutine, tcs));
_ = PluginComponent.Instance.StartCoroutine(new AsTaskCoroutineExecutor(coroutine, tcs));
return tcs.Task; return tcs.Task;
} }
@ -85,7 +85,7 @@ namespace IPA.Utilities.Async
enumerators.Push(coroutine); enumerators.Push(coroutine);
} }
private readonly Stack<IEnumerator> enumerators = new Stack<IEnumerator>(2);
private readonly Stack<IEnumerator> enumerators = new(2);
public object Current => enumerators.FirstOrDefault()?.Current; // effectively a TryPeek public object Current => enumerators.FirstOrDefault()?.Current; // effectively a TryPeek
@ -116,7 +116,7 @@ namespace IPA.Utilities.Async
} }
else else
{ // this enumerator completed, so pop it and continue { // this enumerator completed, so pop it and continue
enumerators.Pop();
_ = enumerators.Pop();
continue; continue;
} }
} }


+ 28
- 10
IPA.Loader/Utilities/Async/UnityMainThreadTaskScheduler.cs View File

@ -1,12 +1,11 @@
using System;
#nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IPA.Utilities.Async namespace IPA.Utilities.Async
@ -20,15 +19,15 @@ namespace IPA.Utilities.Async
/// Gets the default main thread scheduler that is managed by BSIPA. /// Gets the default main thread scheduler that is managed by BSIPA.
/// </summary> /// </summary>
/// <value>a scheduler that is managed by BSIPA</value> /// <value>a scheduler that is managed by BSIPA</value>
public static new TaskScheduler Default { get; } = new UnityMainThreadTaskScheduler();
public static new UnityMainThreadTaskScheduler Default { get; } = new UnityMainThreadTaskScheduler();
/// <summary> /// <summary>
/// Gets a factory for creating tasks on <see cref="Default"/>. /// Gets a factory for creating tasks on <see cref="Default"/>.
/// </summary> /// </summary>
/// <value>a factory for creating tasks on the default scheduler</value> /// <value>a factory for creating tasks on the default scheduler</value>
public static TaskFactory Factory { get; } = new TaskFactory(Default); public static TaskFactory Factory { get; } = new TaskFactory(Default);
private readonly ConcurrentQueue<QueueItem> tasks = new ConcurrentQueue<QueueItem>();
private static readonly ConditionalWeakTable<Task, QueueItem> itemTable = new ConditionalWeakTable<Task, QueueItem>();
private readonly ConcurrentQueue<QueueItem> tasks = new();
private static readonly ConditionalWeakTable<Task, QueueItem> itemTable = new();
private class QueueItem : IEquatable<Task>, IEquatable<QueueItem> private class QueueItem : IEquatable<Task>, IEquatable<QueueItem>
{ {
@ -43,7 +42,9 @@ namespace IPA.Utilities.Async
} }
} }
public Task Task { get; private set; } = null;
public Task? Task { get; private set; }
public Action? Action { get; private set; }
public QueueItem(Task task) public QueueItem(Task task)
{ {
@ -51,7 +52,13 @@ namespace IPA.Utilities.Async
Task = task; Task = task;
} }
public bool Equals(Task other) => HasTask && other.Equals(Task);
public QueueItem(Action action)
{
HasTask = true;
Action = action;
}
public bool Equals(Task? other) => other is not null && HasTask && other.Equals(Task);
public bool Equals(QueueItem other) => other.HasTask == HasTask && Equals(other.Task); public bool Equals(QueueItem other) => other.HasTask == HasTask && Equals(other.Task);
} }
@ -146,7 +153,11 @@ namespace IPA.Utilities.Async
do if (!tasks.TryDequeue(out task)) goto exit; // try dequeue, if we can't exit do if (!tasks.TryDequeue(out task)) goto exit; // try dequeue, if we can't exit
while (!task.HasTask); // if the dequeued task is empty, try again while (!task.HasTask); // if the dequeued task is empty, try again
TryExecuteTask(task.Task);
if (task.Task is not null)
{
_ = TryExecuteTask(task.Task);
}
task.Action?.Invoke();
} }
exit: exit:
sw.Reset(); sw.Reset();
@ -181,7 +192,7 @@ namespace IPA.Utilities.Async
/// <returns>nothing</returns> /// <returns>nothing</returns>
/// <exception cref="NotSupportedException">Always.</exception> /// <exception cref="NotSupportedException">Always.</exception>
protected override IEnumerable<Task> GetScheduledTasks() protected override IEnumerable<Task> GetScheduledTasks()
=> tasks.ToArray().Where(q => q.HasTask).Select(q => q.Task).ToArray();
=> tasks.ToArray().Where(q => q.HasTask).Select(q => q.Task).NonNull().ToArray();
/// <summary> /// <summary>
/// Queues a given <see cref="Task"/> to this scheduler. The <see cref="Task"/> <i>must</i> be /// Queues a given <see cref="Task"/> to this scheduler. The <see cref="Task"/> <i>must</i> be
@ -198,6 +209,13 @@ namespace IPA.Utilities.Async
tasks.Enqueue(item); tasks.Enqueue(item);
} }
internal void QueueAction(Action action)
{
ThrowIfDisposed();
tasks.Enqueue(new(action));
}
/// <summary> /// <summary>
/// Runs the task inline if the current thread is the Unity main thread. /// Runs the task inline if the current thread is the Unity main thread.
/// </summary> /// </summary>


+ 8
- 11
IPA.Loader/Utilities/CriticalSection.cs View File

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using IPA.Logging; using IPA.Logging;
namespace IPA.Utilities namespace IPA.Utilities
@ -21,9 +18,9 @@ namespace IPA.Utilities
ResetExitHandlers(); ResetExitHandlers();
} }
private static void Reset(object sender, EventArgs e)
private static void Reset(object? sender, EventArgs? e)
{ {
Win32.SetConsoleCtrlHandler(registeredHandler, false);
_ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
WinHttp.SetPeekMessageHook(null); WinHttp.SetPeekMessageHook(null);
} }
@ -32,8 +29,8 @@ namespace IPA.Utilities
private static readonly Win32.ConsoleCtrlDelegate registeredHandler = HandleExit; private static readonly Win32.ConsoleCtrlDelegate registeredHandler = HandleExit;
internal static void ResetExitHandlers() internal static void ResetExitHandlers()
{ {
Win32.SetConsoleCtrlHandler(registeredHandler, false);
Win32.SetConsoleCtrlHandler(registeredHandler, true);
_ = Win32.SetConsoleCtrlHandler(registeredHandler, false);
_ = Win32.SetConsoleCtrlHandler(registeredHandler, true);
WinHttp.SetPeekMessageHook(PeekMessageHook); WinHttp.SetPeekMessageHook(PeekMessageHook);
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
@ -60,14 +57,14 @@ namespace IPA.Utilities
[DllImport("bsipa-doorstop")] [DllImport("bsipa-doorstop")]
public static extern void SetPeekMessageHook( public static extern void SetPeekMessageHook(
[MarshalAs(UnmanagedType.FunctionPtr)] [MarshalAs(UnmanagedType.FunctionPtr)]
PeekMessageHook hook);
PeekMessageHook? hook);
[DllImport("bsipa-doorstop")] [DllImport("bsipa-doorstop")]
public static extern void SetIgnoreUnhandledExceptions( public static extern void SetIgnoreUnhandledExceptions(
[MarshalAs(UnmanagedType.Bool)] bool ignore); [MarshalAs(UnmanagedType.Bool)] bool ignore);
} }
private static Win32.ConsoleCtrlDelegate _handler = null;
private static Win32.ConsoleCtrlDelegate? _handler = null;
private static volatile bool isInExecuteSection = false; private static volatile bool isInExecuteSection = false;
// returns true to continue looping and calling PeekMessage // returns true to continue looping and calling PeekMessage


+ 13
- 0
IPA.Loader/Utilities/ReflectionUtil.cs View File

@ -130,6 +130,19 @@ namespace IPA.Utilities
return copy; return copy;
} }
/// <summary>
/// Converts the property name to the one of the compiler-generated backing field.
/// This can be used for the field-based reflection when you want to set the value of a get-only property
/// </summary>
/// <param name="propertyName">Name of the property</param>
/// <returns>Name of the backing field</returns>
/// <remarks>
/// Only works for properties with compiler-generated backing fields.
/// This is only a simple method and doesn't have any guarantees to work 100% of the time across different compilers/runtimes.
/// See <a href="https://github.com/dotnet/roslyn/blob/1497e87d967c5b7797edb5f782131508607139e5/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs#L24-L28">this link</a> for more info.
/// </remarks>
public static string ToCompilerGeneratedBackingField(string propertyName) => $"<{propertyName}>k__BackingField";
private static void CopyForType(Type type, Component source, Component destination) private static void CopyForType(Type type, Component source, Component destination)
{ {
FieldInfo[] myObjectFields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); FieldInfo[] myObjectFields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);


+ 73
- 9
IPA.Loader/Utilities/UnityGame.cs View File

@ -1,6 +1,9 @@
using IPA.Config;
#nullable enable
using IPA.Config;
using IPA.Utilities.Async;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -17,12 +20,12 @@ namespace IPA.Utilities
/// </summary> /// </summary>
public static class UnityGame public static class UnityGame
{ {
private static AlmostVersion _gameVersion;
private static AlmostVersion? _gameVersion;
/// <summary> /// <summary>
/// Provides the current game version. /// Provides the current game version.
/// </summary> /// </summary>
/// <value>the SemVer version of the game</value> /// <value>the SemVer version of the game</value>
public static AlmostVersion GameVersion => _gameVersion ?? (_gameVersion = new AlmostVersion(ApplicationVersionProxy));
public static AlmostVersion GameVersion => _gameVersion ??= new AlmostVersion(ApplicationVersionProxy);
internal static void SetEarlyGameVersion(AlmostVersion ver) internal static void SetEarlyGameVersion(AlmostVersion ver)
{ {
@ -76,24 +79,30 @@ namespace IPA.Utilities
} }
internal static bool IsGameVersionBoundary { get; private set; } internal static bool IsGameVersionBoundary { get; private set; }
internal static AlmostVersion OldVersion { get; private set; }
internal static AlmostVersion? OldVersion { get; private set; }
internal static void CheckGameVersionBoundary() internal static void CheckGameVersionBoundary()
{ {
var gameVer = GameVersion; var gameVer = GameVersion;
var lastVerS = SelfConfig.LastGameVersion_; var lastVerS = SelfConfig.LastGameVersion_;
OldVersion = lastVerS != null ? new AlmostVersion(lastVerS, gameVer) : null; OldVersion = lastVerS != null ? new AlmostVersion(lastVerS, gameVer) : null;
IsGameVersionBoundary = OldVersion != null && gameVer != OldVersion;
IsGameVersionBoundary = OldVersion is not null && gameVer != OldVersion;
SelfConfig.Instance.LastGameVersion = gameVer.ToString(); SelfConfig.Instance.LastGameVersion = gameVer.ToString();
} }
private static Thread mainThread;
private static Thread? mainThread;
/// <summary> /// <summary>
/// Checks if the currently running code is running on the Unity main thread. /// Checks if the currently running code is running on the Unity main thread.
/// </summary> /// </summary>
/// <value><see langword="true"/> if the curent thread is the Unity main thread, <see langword="false"/> otherwise</value> /// <value><see langword="true"/> if the curent thread is the Unity main thread, <see langword="false"/> otherwise</value>
public static bool OnMainThread => Thread.CurrentThread.ManagedThreadId == mainThread?.ManagedThreadId;
public static bool OnMainThread => Environment.CurrentManagedThreadId == mainThread?.ManagedThreadId;
/// <summary>
/// Asynchronously switches the current execution context to the Unity main thread.
/// </summary>
/// <returns>An awaitable which causes any following code to execute on the main thread.</returns>
public static SwitchToUnityMainThreadAwaitable SwitchToMainThreadAsync() => default;
internal static void SetMainThread() internal static void SetMainThread()
=> mainThread = Thread.CurrentThread; => mainThread = Thread.CurrentThread;
@ -120,9 +129,9 @@ namespace IPA.Utilities
/// This only gives a /// This only gives a
/// </remarks> /// </remarks>
/// <value>the type of release this is</value> /// <value>the type of release this is</value>
public static Release ReleaseType => (_releaseCache ?? (_releaseCache = CheckIsSteam() ? Release.Steam : Release.Other)).Value;
public static Release ReleaseType => _releaseCache ??= CheckIsSteam() ? Release.Steam : Release.Other;
private static string _installRoot;
private static string? _installRoot;
/// <summary> /// <summary>
/// Gets the path to the game's install directory. /// Gets the path to the game's install directory.
/// </summary> /// </summary>
@ -165,4 +174,59 @@ namespace IPA.Utilities
&& installDirInfo.Parent?.Parent?.Name == "steamapps"; && installDirInfo.Parent?.Parent?.Name == "steamapps";
} }
} }
/// <summary>
/// An awaitable which, when awaited, switches the current context to the Unity main thread.
/// </summary>
/// <seealso cref="UnityGame.SwitchToMainThreadAsync"/>
[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types",
Justification = "This type should never be compared.")]
public struct SwitchToUnityMainThreadAwaitable
{
/// <summary>
/// Gets the awaiter for this awaitable.
/// </summary>
/// <returns>The awaiter for this awaitable.</returns>
public SwitchToUnityMainThreadAwaiter GetAwaiter() => default;
}
/// <summary>
/// An awaiter which, when awaited, switches the current context to the Unity main thread.
/// </summary>
/// <seealso cref="UnityGame.SwitchToMainThreadAsync"/>
[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types",
Justification = "This type should never be compared.")]
public struct SwitchToUnityMainThreadAwaiter : INotifyCompletion, ICriticalNotifyCompletion
{
private static readonly ContextCallback InvokeAction = static o => ((Action)o!)();
/// <summary>
/// Gets whether or not this awaiter is completed.
/// </summary>
public bool IsCompleted => UnityGame.OnMainThread;
/// <summary>
/// Gets the result of this awaiter.
/// </summary>
public void GetResult() { }
/// <summary>
/// Registers a continuation to be called when this awaiter finishes.
/// </summary>
/// <param name="continuation">The continuation.</param>
public void OnCompleted(Action continuation)
{
var ec = ExecutionContext.Capture();
UnityMainThreadTaskScheduler.Default.QueueAction(() => ExecutionContext.Run(ec, InvokeAction, continuation));
}
/// <summary>
/// Registers a continuation to be called when this awaiter finishes, without capturing the execution context.
/// </summary>
/// <param name="continuation">The continuation.</param>
public void UnsafeOnCompleted(Action continuation)
{
UnityMainThreadTaskScheduler.Default.QueueAction(continuation);
}
}
} }

+ 1
- 1
IPA/Program.cs View File

@ -21,7 +21,7 @@ namespace IPA
Unknown Unknown
} }
public const string FileVersion = "4.2.2.0";
public const string FileVersion = "4.3.0.0";
public static Version Version => Assembly.GetEntryAssembly()!.GetName().Version!; public static Version Version => Assembly.GetEntryAssembly()!.GetName().Version!;


+ 2
- 2
README.md View File

@ -1,3 +1,3 @@
# ![BSIPA](docs/images/banner_dark.svg) [![Build](https://github.com/bsmg/BeatSaber-IPA-Reloaded/workflows/Build/badge.svg)](https://github.com/bsmg/BeatSaber-IPA-Reloaded)
# ![BSIPA](docs/images/banner_dark.svg) [![Build](https://github.com/nike4613/BeatSaber-IPA-Reloaded/workflows/Build/badge.svg)](https://github.com/nike4613/BeatSaber-IPA-Reloaded)
[ALL DOCUMENTATION HAS MOVED HERE](https://bsmg.github.io/BeatSaber-IPA-Reloaded/)
[ALL DOCUMENTATION HAS MOVED HERE](https://nike4613.github.io/BeatSaber-IPA-Reloaded/)

BIN
Refs/UnityEngine.CoreModule.Net4.dll View File


BIN
Refs/UnityEngine.Net4.dll View File


BIN
Refs/UnityEngine.UnityWebRequestModule.Net4.dll View File


+ 13
- 8
docs/articles/command-line.md View File

@ -26,6 +26,16 @@ Here's a quick list of what they are and what they do.
> Makes a console appear with log information at startup. > Makes a console appear with log information at startup.
> >
> Optionally, an explicit process ID can be specified to start the game with an external console. This allows it to be
> launched via Steam without being interrupted by its "Allow game launch?" if launched directly from the `.exe`.
>
> Example for Beat Saber using PowerShell:
>
> ```
> .\steam.exe -applaunch 620980 --verbose $PID
> ```
>
> Do note that this isn't going to work from an elevated terminal.
- `--debug` - `--debug`
@ -35,15 +45,13 @@ Here's a quick list of what they are and what they do.
> This option also forces BSIPA to show all debug messages in the console, as well as where they were called. > This option also forces BSIPA to show all debug messages in the console, as well as where they were called.
> >
> This overrides the config settings `Debug.ShowDebug` and `Debug.ShowCallSource`. > This overrides the config settings `Debug.ShowDebug` and `Debug.ShowCallSource`.
>
- `--trace` - `--trace`
> Enables trace level messages. By default, they do not ever enter the message queue, and thus cost almost nothing. > Enables trace level messages. By default, they do not ever enter the message queue, and thus cost almost nothing.
> When this or the config option is used, they are added and logged with the same rules as Debug messages. > When this or the config option is used, they are added and logged with the same rules as Debug messages.
> >
> This overrides the config setting `Debug.ShowTrace`. > This overrides the config setting `Debug.ShowTrace`.
>
- `--mono-debug` - `--mono-debug`
@ -53,7 +61,6 @@ Here's a quick list of what they are and what they do.
> debugger server running on port 10000 on `localhost`. > debugger server running on port 10000 on `localhost`.
> >
> Implies `--debug`. > Implies `--debug`.
>
- `--server` - `--server`
@ -62,7 +69,6 @@ Here's a quick list of what they are and what they do.
> When paired with `--mono-debug`, this option makes the Mono soft debugger act in server mode. It begins listening on > When paired with `--mono-debug`, this option makes the Mono soft debugger act in server mode. It begins listening on
> port 10000 on any address, and will pause startup (with no window) until a debugger is connected. I recommend using > port 10000 on any address, and will pause startup (with no window) until a debugger is connected. I recommend using
> SDB, but that is a command line debugger and a lot of people don't care for those. > SDB, but that is a command line debugger and a lot of people don't care for those.
>
- `--no-yeet` - `--no-yeet`
@ -73,7 +79,6 @@ Here's a quick list of what they are and what they do.
> behaviour is disabled. > behaviour is disabled.
> >
> Overrides the config setting `YeetMods`. > Overrides the config setting `YeetMods`.
>
- `--condense-logs` - `--condense-logs`
@ -85,11 +90,11 @@ Here's a quick list of what they are and what they do.
> Overrides the config setting `Debug.CondenseModLogs`. > Overrides the config setting `Debug.CondenseModLogs`.
- `--plugin-logs` - `--plugin-logs`
> Causes each plugins' log messages to be written to files in their own folder for ease of debugging. > Causes each plugins' log messages to be written to files in their own folder for ease of debugging.
> >
> This was the default through 4.1.6, however is now disabled by default. > This was the default through 4.1.6, however is now disabled by default.
> >
> Overrides the config setting `Debug.CreateModLogs`. > Overrides the config setting `Debug.CreateModLogs`.
*** ***

+ 10
- 6
docs/articles/start-user.md View File

@ -77,11 +77,14 @@ uid: articles.start.user
Many plugins will come in a zip such that the root of the zip represents the game install directory, so all you may have to Many plugins will come in a zip such that the root of the zip represents the game install directory, so all you may have to
do is extract the plugin into the game installation folder. do is extract the plugin into the game installation folder.
> [!NOTE]
> [!NOTE] For Linux users
>
> By default, WINE loads DLLs differently to Windows, causing issues with the injection. To make BSIPA's injection work
> with Wine, `winhttp` has to have a DLL override set to `native,builtin`. This is best achieved by putting
> `WINEDLLOVERRIDES="winhttp=native,builtin" %command%` in Beat Saber's launch options in Steam.
> >
> For some reason, by default, Wine does not load DLLs in quite the same way that Windows does, causing issues with the injection.
> To make the injection work with Wine, `winhttp` has to have a DLL override set to `native,builtin`. This can be set either through
> Protontricks, or with the following `.reg` file.
> Alternatively, this can be set either through Protontricks, or using the following
> `.reg` file:
> >
> ```reg > ```reg
> REGEDIT4 > REGEDIT4
@ -89,8 +92,9 @@ uid: articles.start.user
> "winhttp"="native,builtin" > "winhttp"="native,builtin"
> ``` > ```
> >
> For Steam there's a per-game Wine prefix under `compatdata`. In this case `SteamLibrary/steamapps/compatdata/620980/pfx/user.reg`.
> Changes to this file will likely be ovewritten when the game updates or if local files are validated through Steam.
> For Steam, each game's Wine prefix is located under `compatdata`; in Beat Saber's case `SteamLibrary/steamapps/compatdata/620980/pfx/user.reg`.
> Changes to this file might be ovewritten when the game or Proton are updated however, so the launch options method is
> recommended.
Thats really all you have to do! The installation should persist across game updates for as long as `winhttp.dll` is present in Thats really all you have to do! The installation should persist across game updates for as long as `winhttp.dll` is present in
the game directory, though your plugins will be moved to a different folder when it does update so things don't break horribly. the game directory, though your plugins will be moved to a different folder when it does update so things don't break horribly.


+ 2
- 2
docs/build.ps1 View File

@ -7,8 +7,8 @@ if ($PSEdition -eq "Core") {
# read SelfConfig, remove wierd bits, load it, load Newtonsoft, and turn it into a schema # read SelfConfig, remove wierd bits, load it, load Newtonsoft, and turn it into a schema
$newtonsoftVer = "12.0.2"
$newtonsoftSchemaVer = "3.0.11"
$newtonsoftVer = "13.0.1"
$newtonsoftSchemaVer = "3.0.15-beta2"
$codeDomProviderVer = "3.6.0" $codeDomProviderVer = "3.6.0"
$roslynVer = "3.10.0" $roslynVer = "3.10.0"
$nugetBase = "$(Get-Location)/nuget" $nugetBase = "$(Get-Location)/nuget"


+ 1
- 1
docs/docfx.json View File

@ -20,7 +20,7 @@
"disableGitFeatures": false, "disableGitFeatures": false,
"disableDefaultFilter": false, "disableDefaultFilter": false,
"properties": { "properties": {
"TargetFramework": "net461"
"TargetFramework": "net472"
} }
}], }],
"build": { "build": {


Loading…
Cancel
Save