@@ -38,12 +38,7 @@ internal static partial class GetExtendedFileAttrib
3838 /// operations.
3939 /// https://learn.microsoft.com/windows/win32/api/winver/nf-winver-getfileversioninfoexw#parameters
4040 /// </summary>
41- private const int FILE_VER_GET_NEUTRAL = 2 ;
42-
43- /// <summary>
44- /// Represents an error code indicating that a specified resource type could not be found. The value is 1813 (-2147023083).
45- /// </summary>
46- private const int ERROR_RESOURCE_TYPE_NOT_FOUND = 1813 ;
41+ private const uint FILE_VER_GET_NEUTRAL = 2 ;
4742
4843 /// <summary>
4944 /// Retrieves extended file information, including version, original file name, internal name, file description, and
@@ -77,16 +72,15 @@ internal static ExFileInfo Get(string filePath)
7772 Span < byte > spanData = new ( versionData ) ;
7873
7974 // Extract version from the version data
80- if ( ! TryGetVersion ( spanData , out Version ? version , out int versionErrorCode ) )
75+ if ( ! TryGetVersion ( spanData , out Version ? version ) )
8176 {
82- // Throw Win32Exception using captured error code
83- throw new Win32Exception ( versionErrorCode ) ;
77+ return BadCaseReturnVal ;
8478 }
8579
8680 // Extract locale and encoding information
87- if ( ! TryGetLocaleAndEncoding ( spanData , out string ? locale , out string ? encoding , out int localeErrorCode ) )
81+ if ( ! TryGetLocaleAndEncoding ( spanData , out string ? locale , out string ? encoding ) )
8882 {
89- throw new Win32Exception ( localeErrorCode ) ;
83+ return BadCaseReturnVal ;
9084 }
9185
9286 // Retrieve various file information based on locale and encoding and return the result
@@ -112,29 +106,24 @@ internal static ExFileInfo Get(string filePath)
112106 }
113107 }
114108
115-
116109 /// <summary>
117110 /// Extracts version information from a byte array and outputs it as a Version object.
118111 /// </summary>
119112 /// <param name="data">The byte array containing version information to be extracted.</param>
120113 /// <param name="version">Outputs the extracted version information as a Version object.</param>
121- /// <param name="win32Error">Outputs the Win32 error code when extraction fails.</param>
122114 /// <returns>Returns true if the version was successfully extracted, otherwise false.</returns>
123- private static unsafe bool TryGetVersion ( Span < byte > data , out Version ? version , out int win32Error )
115+ private static unsafe bool TryGetVersion ( Span < byte > data , out Version ? version )
124116 {
125117 version = null ;
126- win32Error = 0 ;
127118
128119 // pin span directly
129120 fixed ( byte * pData = data )
130121 {
131122 IntPtr basePtr = ( IntPtr ) pData ;
132123
133- // Query the root block for version info
134- if ( ! NativeMethods . VerQueryValueW ( basePtr , "\\ " , out nint buffer , out _ ) )
124+ // Query the root block for version info and make sure the returned length is large enough for the struct
125+ if ( NativeMethods . VerQueryValueW ( basePtr , "\\ " , out nint buffer , out uint len ) == 0 || buffer == IntPtr . Zero || len < sizeof ( VS_FIXEDFILEINFO ) )
135126 {
136- // Capture Win32 error immediately after failure.
137- win32Error = Marshal . GetLastPInvokeError ( ) ;
138127 return false ;
139128 }
140129
@@ -152,40 +141,35 @@ private static unsafe bool TryGetVersion(Span<byte> data, out Version? version,
152141 }
153142 }
154143
155-
156144 /// <summary>
157145 /// Extracts locale and encoding information from a byte span.
158146 /// </summary>
159147 /// <param name="data">The byte span containing data from which locale and encoding are extracted.</param>
160148 /// <param name="locale">Outputs the locale information derived from the data.</param>
161149 /// <param name="encoding">Outputs the encoding information derived from the data.</param>
162- /// <param name="win32Error">Outputs the Win32 error code when extraction fails.</param>
163150 /// <returns>Returns a boolean indicating the success of the extraction process.</returns>
164- private static unsafe bool TryGetLocaleAndEncoding ( Span < byte > data , out string ? locale , out string ? encoding , out int win32Error )
151+ private static unsafe bool TryGetLocaleAndEncoding ( Span < byte > data , out string ? locale , out string ? encoding )
165152 {
166153 locale = null ;
167154 encoding = null ;
168- win32Error = 0 ;
169155
170156 // pin span instead of allocating a new array each call.
171157 fixed ( byte * pData = data )
172158 {
173159 IntPtr basePtr = ( IntPtr ) pData ;
174160
175- // Query the translation block for locale and encoding
176- if ( ! NativeMethods . VerQueryValueW ( basePtr , "\\ VarFileInfo\\ Translation" , out nint buffer , out _ ) )
161+ // Query the translation block for locale and encoding, verifying length is at least 4 bytes (2 WORDs)
162+ if ( NativeMethods . VerQueryValueW ( basePtr , "\\ VarFileInfo\\ Translation" , out nint buffer , out uint len ) == 0 || buffer == IntPtr . Zero || len < 4 )
177163 {
178- win32Error = Marshal . GetLastPInvokeError ( ) ;
179164 return false ;
180165 }
181166
182- // Copy the translation values (two WORDs)
183- short [ ] translations = new short [ 2 ] ;
184- Marshal . Copy ( buffer , translations , 0 , 2 ) ;
167+ // Cast directly to ushort* (WORD)
168+ ushort * pTranslations = ( ushort * ) buffer ;
185169
186170 // Convert the translation values to hex strings
187- locale = translations [ 0 ] . ToString ( "X4" , CultureInfo . InvariantCulture ) ;
188- encoding = translations [ 1 ] . ToString ( "X4" , CultureInfo . InvariantCulture ) ;
171+ locale = pTranslations [ 0 ] . ToString ( "X4" , CultureInfo . InvariantCulture ) ;
172+ encoding = pTranslations [ 1 ] . ToString ( "X4" , CultureInfo . InvariantCulture ) ;
189173 return true ;
190174 }
191175 }
@@ -200,7 +184,7 @@ private static unsafe bool TryGetLocaleAndEncoding(Span<byte> data, out string?
200184 /// <returns>Returns the localized resource string or null if not found.</returns>
201185 private static unsafe string ? GetLocalizedResource ( Span < byte > versionBlock , string encoding , string locale , string resource )
202186 {
203- string [ ] encodings = [ encoding , Cp1252FallbackCode , UnicodeFallbackCode ] ;
187+ ReadOnlySpan < string > encodings = [ encoding , Cp1252FallbackCode , UnicodeFallbackCode ] ;
204188
205189 // pin once, reuse base pointer across attempts.
206190 fixed ( byte * pData = versionBlock )
@@ -209,23 +193,22 @@ private static unsafe bool TryGetLocaleAndEncoding(Span<byte> data, out string?
209193
210194 foreach ( string enc in encodings )
211195 {
212- string subBlock = $ "StringFileInfo\\ { locale } { enc } { resource } ";
213-
214- if ( NativeMethods . VerQueryValueW ( basePtr , subBlock , out nint buffer , out _ ) )
215- return Marshal . PtrToStringAuto ( buffer ) ;
196+ string subBlock = $ "\\ StringFileInfo\\ { locale } { enc } { resource } ";
216197
217- // Capture the VerQueryValueW's error immediately if it failed above.
218- int lastError = Marshal . GetLastPInvokeError ( ) ;
198+ // Grab the length to safely instantiate the string
199+ if ( NativeMethods . VerQueryValueW ( basePtr , subBlock , out nint buffer , out uint len ) != 0 && buffer != IntPtr . Zero && len > 0 )
200+ {
201+ ReadOnlySpan < char > valueSpan = new ( ( void * ) buffer , ( int ) len ) ;
202+ valueSpan = valueSpan . TrimEnd ( '\0 ' ) ;
219203
220- // If error is not resource type not found, throw the error
221- if ( lastError != ERROR_RESOURCE_TYPE_NOT_FOUND )
222- throw new Win32Exception ( lastError ) ;
204+ // If the resulting string is empty, return the interned string.Empty to avoid allocating a new object on the heap
205+ return valueSpan . IsEmpty ? string . Empty : new string ( valueSpan ) ;
206+ }
223207 }
224208 }
225209 return null ;
226210 }
227211
228-
229212 /// <summary>
230213 /// Check if a string is null or whitespace and return null if it is
231214 /// </summary>
0 commit comments