1- using AnakinRaW . CommonUtilities . FileSystem ;
2- using Microsoft . Extensions . Logging ;
3- using PG . StarWarsGame . Engine . IO . Utilities ;
1+ using Microsoft . Extensions . Logging ;
42using PG . StarWarsGame . Engine . Utilities ;
53using PG . StarWarsGame . Files . MEG . Binary ;
64using System ;
75using System . Collections . Generic ;
86using System . Diagnostics ;
97using System . Diagnostics . CodeAnalysis ;
108using System . IO ;
11- using System . Runtime . CompilerServices ;
12- using System . Runtime . InteropServices ;
139
1410namespace PG . StarWarsGame . Engine . IO . Repositories ;
1511
@@ -21,7 +17,7 @@ public bool FileExists(string filePath, string[] extensions, bool megFileOnly =
2117 {
2218 foreach ( var extension in extensions )
2319 {
24- var newPath = FileSystem . Path . ChangeExtension ( filePath , extension ) ;
20+ var newPath = PGFileSystem . ChangeExtension ( filePath , extension ) ;
2521 if ( FileExists ( newPath , megFileOnly ) )
2622 return true ;
2723 }
@@ -89,7 +85,14 @@ public Stream OpenFile(ReadOnlySpan<char> filePath, bool megFileOnly = false)
8985 sb . Dispose ( ) ;
9086 return fileStream ;
9187 }
92-
88+
89+ /// <summary>
90+ /// The core routine for finding a file using the game's specific lookup rules.
91+ /// </summary>
92+ /// <param name="filePath">The file path.</param>
93+ /// <param name="pathStringBuilder">The string builder used for constructing the file path.</param>
94+ /// <param name="megFileOnly">Whether to only search for files in MEG archives.</param>
95+ /// <returns>The file found information.</returns>
9396 protected internal abstract FileFoundInfo FindFile ( ReadOnlySpan < char > filePath ,
9497 ref ValueStringBuilder pathStringBuilder , bool megFileOnly = false ) ;
9598
@@ -99,11 +102,11 @@ protected FileFoundInfo GetFileInfoFromMasterMeg(ReadOnlySpan<char> filePath)
99102
100103 var sb = new ValueStringBuilder ( stackalloc char [ Math . Max ( filePath . Length , PGConstants . MaxMegEntryPathLength ) ] ) ;
101104 sb . Append ( filePath ) ;
102- NormalizePath ( ref sb ) ;
105+ PGFileSystem . NormalizePath ( ref sb ) ;
103106
104107 if ( sb . Length > PGConstants . MaxMegEntryPathLength )
105108 {
106- Logger . LogWarning ( "Trying to open a MEG entry which is longer than 259 characters: '{FileName}'" , sb . ToString ( ) ) ;
109+ _logger . LogWarning ( "Trying to open a MEG entry which is longer than 259 characters: '{FileName}'" , sb . ToString ( ) ) ;
107110 sb . Dispose ( ) ;
108111 return default ;
109112 }
@@ -129,41 +132,8 @@ protected FileFoundInfo GetFileInfoFromMasterMeg(ReadOnlySpan<char> filePath)
129132
130133 protected FileFoundInfo FindFileCore ( ReadOnlySpan < char > filePath , ref ValueStringBuilder stringBuilder )
131134 {
132- bool exists ;
133-
134- stringBuilder . Length = 0 ;
135-
136- if ( FileSystem . Path . IsPathFullyQualified ( filePath ) )
137- stringBuilder . Append ( filePath ) ;
138- else
139- FileSystem . Path . Join ( GameDirectory . AsSpan ( ) , filePath , ref stringBuilder ) ;
140-
141-
142- if ( ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
143- {
144- NormalizePath ( ref stringBuilder ) ;
145-
146- var actualFilePath = stringBuilder . AsSpan ( ) ;
147- exists = FileSystemPathExistsCaseInsensitive ( actualFilePath , ref stringBuilder ) ;
148- }
149- else
150- {
151- // We *could* also use the slightly faster GetFileAttributesA.
152- // However, CreateFileA and GetFileAttributesA are implemented complete independent.
153- // The game uses CreateFileA.
154- // Thus, we should stick to what the game uses in order to be as close to the engine as possible
155- // NB: It's also important that the string builder is zero-terminated, as otherwise CreateFileA might get invalid data.
156- var fileHandle = CreateFile (
157- in stringBuilder . GetPinnableReference ( true ) ,
158- FileAccess . Read ,
159- FileShare . Read ,
160- IntPtr . Zero ,
161- FileMode . Open ,
162- FileAttributes . Normal , IntPtr . Zero ) ;
163-
164- exists = IsValidAndClose ( fileHandle ) ;
165- }
166- return ! exists ? new FileFoundInfo ( ) : new FileFoundInfo ( stringBuilder . AsSpan ( ) ) ;
135+ var exists = PGFileSystem . FileExists ( filePath , ref stringBuilder , GameDirectory . AsSpan ( ) ) ;
136+ return ! exists ? default : new FileFoundInfo ( stringBuilder . AsSpan ( ) ) ;
167137 }
168138
169139 protected FileFoundInfo FileFromAltExists ( ReadOnlySpan < char > filePath , IList < string > fallbackPaths , ref ValueStringBuilder pathStringBuilder )
@@ -179,7 +149,7 @@ protected FileFoundInfo FileFromAltExists(ReadOnlySpan<char> filePath, IList<str
179149 {
180150 pathStringBuilder . Length = 0 ;
181151
182- FileSystem . Path . Join ( fallbackPath . AsSpan ( ) , pathWithNormalizedData , ref pathStringBuilder ) ;
152+ PGFileSystem . JoinPath ( fallbackPath . AsSpan ( ) , pathWithNormalizedData , ref pathStringBuilder ) ;
183153 var newPath = pathStringBuilder . AsSpan ( ) ;
184154
185155 var fileFoundInfo = FindFileCore ( newPath , ref pathStringBuilder ) ;
@@ -190,79 +160,6 @@ protected FileFoundInfo FileFromAltExists(ReadOnlySpan<char> filePath, IList<str
190160 return default ;
191161 }
192162
193- private static int NormalizePath ( Span < char > path )
194- {
195- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
196- return path . Length ;
197-
198- var writePos = 0 ;
199- var lastWasSeparator = false ;
200- for ( var i = 0 ; i < path . Length ; i ++ )
201- {
202- var c = path [ i ] ;
203- var isSeparator = c is '\\ ' or '/' ;
204- if ( isSeparator && lastWasSeparator )
205- continue ;
206- path [ writePos ++ ] = isSeparator ? '/' : c ;
207- lastWasSeparator = isSeparator ;
208- }
209-
210- return writePos ;
211- }
212-
213- private static void NormalizePath ( ref ValueStringBuilder stringBuilder )
214- {
215- stringBuilder . Length = NormalizePath ( stringBuilder . RawChars . Slice ( 0 , stringBuilder . Length ) ) ;
216- }
217-
218- private bool FileSystemPathExistsCaseInsensitive ( ReadOnlySpan < char > filePath , ref ValueStringBuilder stringBuilder )
219- {
220- var pathString = filePath . ToString ( ) ;
221- if ( FileSystem . File . Exists ( pathString ) )
222- return true ;
223-
224- var directory = FileSystem . Path . GetDirectoryName ( pathString ) ;
225- var fileName = FileSystem . Path . GetFileName ( pathString ) ;
226-
227- if ( string . IsNullOrEmpty ( directory ) || string . IsNullOrEmpty ( fileName ) )
228- return false ;
229-
230- if ( ! FileSystem . Directory . Exists ( directory ) )
231- {
232- if ( ! FileSystemPathExistsCaseInsensitive ( directory . AsSpan ( ) , ref stringBuilder ) )
233- return false ;
234-
235- directory = stringBuilder . AsSpan ( ) . ToString ( ) ;
236- }
237-
238- var files = FileSystem . Directory . GetFiles ( directory ) ;
239- var directories = FileSystem . Directory . GetDirectories ( directory ) ;
240-
241- foreach ( var file in files )
242- {
243- var name = FileSystem . Path . GetFileName ( file ) ;
244- if ( name . Equals ( fileName , StringComparison . OrdinalIgnoreCase ) )
245- {
246- stringBuilder . Length = 0 ;
247- stringBuilder . Append ( file ) ;
248- return true ;
249- }
250- }
251-
252- foreach ( var dir in directories )
253- {
254- var name = FileSystem . Path . GetFileName ( dir ) ;
255- if ( name . Equals ( fileName , StringComparison . OrdinalIgnoreCase ) )
256- {
257- stringBuilder . Length = 0 ;
258- stringBuilder . Append ( dir ) ;
259- return true ;
260- }
261- }
262-
263- return false ;
264- }
265-
266163 private static bool PathStartsWithDataDirectory ( ReadOnlySpan < char > path , out int cutoffLength )
267164 {
268165 cutoffLength = 0 ;
@@ -288,29 +185,6 @@ private static bool PathStartsWithDataDirectory(ReadOnlySpan<char> path, out int
288185 if ( fileFoundInfo . InMeg )
289186 return _megExtractor . GetData ( fileFoundInfo . MegDataEntryReference . Location ) ;
290187
291- return FileSystem . FileStream . New ( fileFoundInfo . FilePath . ToString ( ) , FileMode . Open , FileAccess . Read , FileShare . Read ) ;
292- }
293-
294- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
295- private static bool IsValidAndClose ( IntPtr handle )
296- {
297- var isValid = handle != IntPtr . Zero && handle != new IntPtr ( - 1 ) ;
298- if ( isValid )
299- CloseHandle ( handle ) ;
300- return isValid ;
188+ return PGFileSystem . OpenRead ( fileFoundInfo . FilePath . ToString ( ) ) ;
301189 }
302-
303- [ DllImport ( "kernel32.dll" , SetLastError = true , CharSet = CharSet . Auto ) ]
304- private static extern IntPtr CreateFile (
305- in char lpFileName ,
306- [ MarshalAs ( UnmanagedType . U4 ) ] FileAccess access ,
307- [ MarshalAs ( UnmanagedType . U4 ) ] FileShare share ,
308- IntPtr securityAttributes ,
309- [ MarshalAs ( UnmanagedType . U4 ) ] FileMode creationDisposition ,
310- [ MarshalAs ( UnmanagedType . U4 ) ] FileAttributes flagsAndAttributes ,
311- IntPtr templateFile ) ;
312-
313- [ DllImport ( "kernel32.dll" , SetLastError = true ) ]
314- [ return : MarshalAs ( UnmanagedType . Bool ) ]
315- private static extern bool CloseHandle ( IntPtr hObject ) ;
316190}
0 commit comments