LethalCompanyModding / UnityDebuggerAssistant

Captures a list of all patched methods to help debug unity logs for BepinEx plugins
MIT License
1 stars 1 forks source link

GetAssemblyByName Bug - Sequence contains more than one matching element #1

Closed p1xel8ted closed 1 month ago

p1xel8ted commented 1 month ago

Using this plugin for Cult of the Lamb - but it was erroring every exception due to it finding multiple of the same assembly name.

image

[Error  :UnityDebuggerAssistant] The exception handler failed while running. Please report the next line to Robyn:
[Error  :UnityDebuggerAssistant] System.TypeInitializationException: The type initializer for 'UnityDebuggerAssistant.Utils.UDAExceptionHandler' threw an exception. ---> System.InvalidOperationException: Sequence contains more than one matching element
  at System.Linq.Enumerable.SingleOrDefault[TSource] (System.Collections.Generic.IEnumerable`1[T] source, System.Func`2[T,TResult] predicate) [0x0004a] in <1c318258bf0843289b0e2cbe692fee39>:0
  at UnityDebuggerAssistant.Utils.UDAExceptionHandler.GetAssemblyByName (System.String name) [0x0000e] in ./src/UnityDebuggerAssistant/Utils/ExceptionHandler.cs:177
  at UnityDebuggerAssistant.Utils.UDAExceptionHandler..cctor () [0x00000] in ./src/UnityDebuggerAssistant/Utils/ExceptionHandler.cs:5
   --- End of inner exception stack trace ---
  at UnityDebuggerAssistant.Patches.ExceptionProcessor.Run (System.Exception __instance) [0x00059] in ./src/UnityDebuggerAssistant/Patches/Exception.cs:51

Original code:

    static Assembly GetAssemblyByName(string name)
    {
        return AppDomain.CurrentDomain.GetAssemblies().
               SingleOrDefault(assembly => assembly.GetName().Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
    }

Replacement code:

    private static Assembly GetAssemblyByName(string name)
    {
#if DEBUG
        var list = AppDomain.CurrentDomain.GetAssemblies().Where(assembly => assembly.GetName().Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).ToList();
        list = list.Distinct().ToList();
        foreach (var assembly in list)
        {
            Plugin.Log.LogWarning($"Found assembly: {assembly.FullName} - {assembly.Location}");
        }
#endif
        //changed this to FirstOrDefault because it was throwing an exception using SingleOrDefault (as it was finding multiple assemblies with the same name)
        var toBeReturned = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(assembly => assembly.GetName().Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
#if DEBUG
        Plugin.Log.LogWarning($"Returning assembly: {toBeReturned?.FullName} - {toBeReturned?.Location}");
#endif
        return toBeReturned;
    }
RobynLlama commented 1 month ago

I just tested 1.3.2 before I pushed it to the TS and did not notice this in my log. I just now booted the game up and let it idle for a minute or two on the main menu and my player.log does not contain the word "warning" at all. The only time I log a warning in the whole project is when the processor is busy so I dunno how you got this message or where to start working on it.

p1xel8ted commented 1 month ago

I just tested 1.3.2 before I pushed it to the TS and did not notice this in my log. I just now booted the game up and let it idle for a minute or two on the main menu and my player.log does not contain the word "warning" at all. The only time I log a warning in the whole project is when the processor is busy so I dunno how you got this message or where to start working on it.

No...I added it so I could see what duplicates it was complaining about. Using SingleOrDefault when there is more than one result (why there is more than one, I haven't looked too deeply) is the cause of the exception.

The code in the "Replacement Code" box resolves the issue.

AlbinoGeek commented 1 month ago

Your error message can't exist in either our, nor your code. Your code shows "Found Assembly" printing both the name and location of each Assembly, yet the screenshot shows no locations. If you can reproduce this issue, please show output which matches the code you show, versus at the moment where the screenshot does not match the shown code.

Neither the original nor replacement code could possibly result in the screenshot provided.

This means your installation specifically has two Assembly which call themselves Assembly-CSharp However, yes, you are correct, in the non-standard case where multiple assemblies of the same name exist, FirstOrDefault is more suitable.

Thank you for pointing this out.

2c2
<                SingleOrDefault(assembly => assembly.GetName().Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
---
>                 FirstOrDefault(assembly => assembly.GetName().Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
p1xel8ted commented 1 month ago

Your error message can't exist in either our, nor your code. Your code shows "Found Assembly" printing both the name and location of each Assembly, yet the screenshot shows no locations. If you can reproduce this issue, please show output which matches the code you show, versus at the moment where the screenshot does not match the shown code.

Neither the original nor replacement code could possibly result in the screenshot provided.

This means your installation specifically has two Assembly which call themselves Assembly-CSharp However, yes, you are correct, in the non-standard case where multiple assemblies of the same name exist, FirstOrDefault is more suitable.

Thank you for pointing this out.

2c2
<                SingleOrDefault(assembly => assembly.GetName().Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
---
>                 FirstOrDefault(assembly => assembly.GetName().Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));

As you can see from the thread, I added the logging specifically to capture the assemblies being found. The screenshot was taken before I updated the code to display the locations.

The line below is what produces the output shown in the screenshot:

Plugin.Log.LogWarning($"Found assembly: {assembly}");

I added the location because I too suspected I had a duplicate assembly, but of the two found, one assembly had a location, and one didn't.

Regardless, FirstOrDefault resolves it either way.

Hopefully, this explanation resolves any confusion.