@@ -26,7 +26,7 @@ namespace Nitrox.Server.Subnautica;
2626[ SuppressMessage ( "Usage" , "DIMA001:Dependency Injection container is used directly" ) ]
2727public class Program
2828{
29- private static Lazy < string > gameInstallDir ;
29+ private static Lazy < string > ? gameInstallDir ;
3030 private static readonly CircularBuffer < string > inputHistory = new ( 1000 ) ;
3131 private static int currentHistoryIndex ;
3232 private static readonly CancellationTokenSource serverCts = new ( ) ;
@@ -84,19 +84,7 @@ private static async Task StartServer(string[] args)
8484 Stopwatch watch = Stopwatch. StartNew ( ) ;
8585
8686 // Allow game path to be given as command argument
87- string gameDir ;
88- if ( args . Length > 0 && Directory . Exists ( args [ 0 ] ) && File . Exists ( Path . Combine ( args [ 0 ] , GameInfo . Subnautica . ExeName ) ) )
89- {
90- gameDir = Path. GetFullPath ( args [ 0 ] ) ;
91- gameInstallDir = new Lazy< string > ( ( ) => gameDir ) ;
92- }
93- else
94- {
95- gameInstallDir = new Lazy < string > ( ( ) =>
96- {
97- return gameDir = NitroxUser . GamePath ;
98- } ) ;
99- }
87+ gameInstallDir = new Lazy < string > ( ( ) => NitroxUser . GamePath ) ;
10088 Log. Info ( $ "Using game files from: \' { gameInstallDir . Value } \' ") ;
10189
10290 // TODO: Fix DI to not be slow (should not use IO in type constructors). Instead, use Lazy<T> (et al). This way, cancellation can be faster.
@@ -107,7 +95,7 @@ private static async Task StartServer(string[] args)
10795 {
10896 _ = ipc . SendOutput ( $ "{ Ipc . Messages . PlayerCountMessage } :[{ count } ]") ;
10997 } ;
110- string serverSaveName = NitroxServer . Server . GetSaveName ( args , "My World" ) ;
98+ string serverSaveName = NitroxServer . Server . GetSaveName ( args ) ;
11199 Log . SaveName = serverSaveName ;
112100
113101 using ( CancellationTokenSource portWaitCts = CancellationTokenSource. CreateLinkedTokenSource ( serverCts . Token ) )
@@ -525,11 +513,11 @@ private static void CurrentDomainOnUnhandledException(object sender, UnhandledEx
525513 private static class AssemblyResolver
526514 {
527515 private static string currentExecutableDirectory;
528- private static readonly Dictionary< string , Assembly > resolvedAssemblyCache = [ ] ;
516+ private static readonly Dictionary< string , AssemblyCacheEntry > resolvedAssemblyCache = [ ] ;
529517
530- public static Assembly Handler ( object sender , ResolveEventArgs args )
518+ public static Assembly ? Handler ( object sender , ResolveEventArgs args )
531519 {
532- static Assembly ResolveFromLib ( ReadOnlySpan < char > dllName )
520+ static Assembly ? ResolveFromLib ( ReadOnlySpan < char > dllName )
533521 {
534522 dllName = dllName . Slice ( 0 , Math . Max ( dllName . IndexOf ( ',' ) , 0 ) ) ;
535523 if ( dllName . IsEmpty )
@@ -546,35 +534,48 @@ static Assembly ResolveFromLib(ReadOnlySpan<char> dllName)
546534 }
547535 string dllNameStr = dllName. ToString( ) ;
548536 // If available, return cached assembly
549- if ( resolvedAssemblyCache . TryGetValue ( dllNameStr , out Assembly val ) )
537+ if ( resolvedAssemblyCache . TryGetValue ( dllNameStr , out AssemblyCacheEntry cacheEntry ) && cacheEntry is { Assembly : { } cachedAssembly } )
550538 {
551- return val ;
539+ return cachedAssembly ;
540+ }
541+ if ( cacheEntry = = null )
542+ {
543+ cacheEntry = new AssemblyCacheEntry ( 0 , null ) ;
544+ resolvedAssemblyCache [ dllNameStr ] = cacheEntry ;
552545 }
553546
554547 // Load DLLs where this program (exe) is located
555548 string dllPath = Path . Combine ( GetExecutableDirectory ( ) , "lib", dllNameStr) ;
556549 // Prefer to use Newtonsoft dll from game instead of our own due to protobuf issues. TODO: Remove when we do our own deserialization of game data instead of using the game's protobuf.
557550 if ( dllPath. IndexOf( "Newtonsoft.Json.dll" , StringComparison . OrdinalIgnoreCase ) >= 0 || ! File . Exists ( dllPath ) )
558551 {
559- // Try find game managed libraries
560- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
552+ if ( gameInstallDir != null )
561553 {
562- dllPath = Path . Combine ( gameInstallDir . Value , "Resources" , "Data" , "Managed" , dllNameStr ) ;
563- }
564- else
565- {
566- dllPath = Path . Combine ( gameInstallDir . Value , "Subnautica_Data" , "Managed" , dllNameStr ) ;
554+ // Try find game managed libraries
555+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
556+ {
557+ dllPath = Path . Combine ( gameInstallDir . Value , "Resources" , "Data" , "Managed" , dllNameStr ) ;
558+ }
559+ else
560+ {
561+ dllPath = Path . Combine ( gameInstallDir . Value , "Subnautica_Data" , "Managed" , dllNameStr ) ;
562+ }
567563 }
568564 }
569565
570566 try
571567 {
572568 // Read assemblies as bytes as to not lock the file so that Nitrox can patch assemblies while server is running.
573- Assembly assembly = Assembly. Load( File . ReadAllBytes ( dllPath ) ) ;
574- return resolvedAssemblyCache [ dllNameStr ] = assembly ;
569+ cacheEntry . Assembly = Assembly . Load ( File . ReadAllBytes ( dllPath ) ) ;
570+ return cacheEntry . Assembly ;
575571 }
576572 catch
577573 {
574+ cacheEntry. Attempts ++ ;
575+ if ( cacheEntry . Attempts >= 5 )
576+ {
577+ throw new FileNotFoundException( $ "Failed to load DLL '{ dllName } ' at: { dllPath } ") ;
578+ }
578579 return null ;
579580 }
580581 }
@@ -602,5 +603,11 @@ private static string GetExecutableDirectory()
602603 }
603604 return currentExecutableDirectory = new Uri( Path . GetDirectoryName ( pathAttempt ?? "." ) ?? Directory . GetCurrentDirectory ( ) ) . LocalPath ;
604605 }
606+
607+ private record AssemblyCacheEntry( int Attempts , Assembly ? Assembly )
608+ {
609+ public int Attempts { get ; set ; } = Attempts;
610+ public Assembly? Assembly { get ; set ; } = Assembly ;
611+ }
605612 }
606613}
0 commit comments