55using System . IO ;
66using System . Text ;
77
8- namespace SourceGit . Commands
8+ namespace SourceGit . Utilities
99{
1010 /// <summary>
1111 /// Decodes human-readable names from Unreal Engine OFPA (One File Per Actor) .uasset files.
@@ -20,14 +20,20 @@ namespace SourceGit.Commands
2020 /// Compatibility: UE 4.26 - 5.7+
2121 /// Performance: ~0.1 ms/file
2222 /// </summary>
23- public static class DecodeOFPAPath
23+ /// <summary>
24+ /// Decodes human-readable names from Unreal Engine OFPA (One File Per Actor) .uasset files.
25+ /// These files have hashed names like "KCBX0GWLTFQT9RJ8M1LY8.uasset" in __ExternalActors__ folders.
26+ /// </summary>
27+ public static class OFPAParser
2428 {
2529 // Unreal Engine asset magic number (little-endian: 0x9E2A83C1)
2630 private static readonly byte [ ] UnrealMagic = { 0xC1 , 0x83 , 0x2A , 0x9E } ;
2731
2832 private const int HeaderScanLimit = 1024 ;
2933 private const int MaxStringLength = 256 ;
3034 private const int PropertyTagWindow = 150 ;
35+ private const int MinimumHeaderSize = 20 ;
36+ private const int PatternLength = 16 ;
3137
3238 /// <summary>
3339 /// Result of decoding an OFPA file.
@@ -77,6 +83,9 @@ public static bool IsOFPAFile(string path)
7783 {
7884 try
7985 {
86+ if ( ! File . Exists ( filePath ) )
87+ return null ;
88+
8089 var data = File . ReadAllBytes ( filePath ) ;
8190 return DecodeFromData ( data ) ;
8291 }
@@ -93,7 +102,7 @@ public static bool IsOFPAFile(string path)
93102 /// <returns>Decoded label or null if data is invalid</returns>
94103 public static DecodeResult ? DecodeFromData ( byte [ ] data )
95104 {
96- if ( data == null || data . Length < 20 )
105+ if ( data == null || data . Length < MinimumHeaderSize )
97106 return null ;
98107
99108 // Check magic number
@@ -104,48 +113,47 @@ public static bool IsOFPAFile(string path)
104113 return ParseUAsset ( data ) ;
105114 }
106115
107- private static DecodeResult ? ParseUAsset ( byte [ ] data )
116+ private static DecodeResult ? ParseUAsset ( byte [ ] buffer )
108117 {
109- int size = data . Length ;
118+ int fileSize = buffer . Length ;
110119
111120 // Fast path: find '/' to locate FolderName string
112- int start = 20 ;
113- int slashOff = FindByte ( data , ( byte ) '/' , 20 , Math . Min ( size , HeaderScanLimit ) ) ;
114- if ( slashOff >= 24 )
121+ int searchStart = MinimumHeaderSize ;
122+ int slashOffset = FindByte ( buffer , ( byte ) '/' , MinimumHeaderSize , Math . Min ( fileSize , HeaderScanLimit ) ) ;
123+ if ( slashOffset >= 24 )
115124 {
116- int pLen = ReadInt32 ( data , slashOff - 4 ) ;
117- if ( pLen > 0 && pLen < MaxStringLength )
118- start = slashOff - 4 ;
125+ int length = ReadInt32 ( buffer , slashOffset - 4 ) ;
126+ if ( length > 0 && length < MaxStringLength )
127+ searchStart = slashOffset - 4 ;
119128 }
120129
121- // Scan for name_count and name_offset
122- int headerLen = Math . Min ( size , HeaderScanLimit ) ;
123- int limit = headerLen - 20 ;
130+ // Scan for NameMap count and offset
131+ int headerLength = Math . Min ( fileSize , HeaderScanLimit ) ;
132+ int scanLimit = headerLength - MinimumHeaderSize ;
124133 int nameCount = 0 ;
125134 int nameOffset = 0 ;
126135
127- for ( int off = start ; off < limit ; off ++ )
136+ for ( int currentPos = searchStart ; currentPos < scanLimit ; currentPos ++ )
128137 {
129- int pLen = ReadInt32 ( data , off ) ;
130- if ( pLen > 0 && pLen < MaxStringLength )
138+ int stringLength = ReadInt32 ( buffer , currentPos ) ;
139+ if ( stringLength > 0 && stringLength < MaxStringLength )
131140 {
132- int strEnd = off + 4 + pLen ;
133- if ( strEnd > limit )
141+ int stringEnd = currentPos + 4 + stringLength ;
142+ if ( stringEnd > scanLimit )
134143 break ;
135144
136- byte ch = data [ off + 4 ] ;
145+ byte firstChar = buffer [ currentPos + 4 ] ;
137146 // Check for '/' or "None"
138- if ( ch == 47 || ( ch == 78 && MatchBytes ( data , off + 4 , "None" ) ) )
147+ if ( firstChar == 47 || ( firstChar == 78 && MatchBytes ( buffer , currentPos + 4 , "None" ) ) )
139148 {
140- int baseOff = strEnd ;
141- if ( baseOff + 12 <= headerLen )
149+ if ( stringEnd + 12 <= headerLength )
142150 {
143- int nc = ReadInt32 ( data , baseOff + 4 ) ;
144- int no = ReadInt32 ( data , baseOff + 8 ) ;
145- if ( nc > 0 && nc < 100000 && no > 0 && no < size )
151+ int count = ReadInt32 ( buffer , stringEnd + 4 ) ;
152+ int offset = ReadInt32 ( buffer , stringEnd + 8 ) ;
153+ if ( count > 0 && count < 100000 && offset > 0 && offset < fileSize )
146154 {
147- nameCount = nc ;
148- nameOffset = no ;
155+ nameCount = count ;
156+ nameOffset = offset ;
149157 break ;
150158 }
151159 }
@@ -157,118 +165,118 @@ public static bool IsOFPAFile(string path)
157165 return null ;
158166
159167 // Parse Name Map - find target indices
160- int labelIdx = - 1 ;
161- int strIdx = - 1 ;
168+ int labelIndex = - 1 ;
169+ int propertyIndex = - 1 ;
162170 string ? labelType = null ;
163171
164- int pos = nameOffset ;
165- for ( int i = 0 ; i < nameCount && pos + 4 <= size ; i ++ )
172+ int position = nameOffset ;
173+ for ( int i = 0 ; i < nameCount && position + 4 <= fileSize ; i ++ )
166174 {
167- int sLen = ReadInt32 ( data , pos ) ;
168- pos += 4 ;
175+ int stringLength = ReadInt32 ( buffer , position ) ;
176+ position += 4 ;
169177
170- if ( sLen > 0 )
178+ if ( stringLength > 0 )
171179 {
172- int end = pos + sLen ;
173- if ( end > size )
180+ int end = position + stringLength ;
181+ if ( end > fileSize )
174182 break ;
175183
176184 // Check for target strings
177- if ( sLen == 11 && labelIdx < 0 && MatchBytes ( data , pos , "ActorLabel" ) )
185+ if ( stringLength == 11 && labelIndex < 0 && MatchBytes ( buffer , position , "ActorLabel" ) )
178186 {
179- labelIdx = i ;
187+ labelIndex = i ;
180188 labelType = "ActorLabel" ;
181189 }
182- else if ( sLen == 12 )
190+ else if ( stringLength == 12 )
183191 {
184- if ( MatchBytes ( data , pos , "StrProperty" ) )
192+ if ( MatchBytes ( buffer , position , "StrProperty" ) )
185193 {
186- strIdx = i ;
194+ propertyIndex = i ;
187195 }
188- else if ( labelIdx < 0 && MatchBytes ( data , pos , "FolderLabel" ) )
196+ else if ( labelIndex < 0 && MatchBytes ( buffer , position , "FolderLabel" ) )
189197 {
190- labelIdx = i ;
198+ labelIndex = i ;
191199 labelType = "FolderLabel" ;
192200 }
193201 }
194- else if ( sLen == 6 && labelIdx < 0 && MatchBytes ( data , pos , "Label" ) )
202+ else if ( stringLength == 6 && labelIndex < 0 && MatchBytes ( buffer , position , "Label" ) )
195203 {
196- labelIdx = i ;
204+ labelIndex = i ;
197205 labelType = "Label" ;
198206 }
199207
200- pos = end ;
208+ position = end ;
201209
202- if ( labelIdx >= 0 && strIdx >= 0 )
210+ if ( labelIndex >= 0 && propertyIndex >= 0 )
203211 break ;
204212 }
205- else if ( sLen < 0 )
213+ else if ( stringLength < 0 )
206214 {
207215 // UTF-16 string
208- pos += ( - sLen ) * 2 ;
216+ position += ( - stringLength ) * 2 ;
209217 }
210218
211219 // Skip hash value if present
212- if ( pos + 4 <= size )
220+ if ( position + 4 <= fileSize )
213221 {
214- int nv = ReadInt32 ( data , pos ) ;
215- if ( nv == 0 || nv < - 512 || nv > 512 )
216- pos += 4 ;
222+ int hash = ReadInt32 ( buffer , position ) ;
223+ if ( hash == 0 || hash < - 512 || hash > 512 )
224+ position += 4 ;
217225 }
218226 }
219227
220- if ( labelIdx < 0 || strIdx < 0 || labelType == null )
228+ if ( labelIndex < 0 || propertyIndex < 0 || labelType == null )
221229 return null ;
222230
223- // Find property tag pattern: [labelIdx , 0, strIdx , 0]
224- byte [ ] pattern = new byte [ 16 ] ;
225- BinaryPrimitives . WriteInt32LittleEndian ( pattern . AsSpan ( 0 ) , labelIdx ) ;
231+ // Find property tag pattern: [labelIndex , 0, propertyIndex , 0]
232+ byte [ ] pattern = new byte [ PatternLength ] ;
233+ BinaryPrimitives . WriteInt32LittleEndian ( pattern . AsSpan ( 0 ) , labelIndex ) ;
226234 BinaryPrimitives . WriteInt32LittleEndian ( pattern . AsSpan ( 4 ) , 0 ) ;
227- BinaryPrimitives . WriteInt32LittleEndian ( pattern . AsSpan ( 8 ) , strIdx ) ;
235+ BinaryPrimitives . WriteInt32LittleEndian ( pattern . AsSpan ( 8 ) , propertyIndex ) ;
228236 BinaryPrimitives . WriteInt32LittleEndian ( pattern . AsSpan ( 12 ) , 0 ) ;
229237
230- int tagOff = FindPattern ( data , pattern ) ;
231- if ( tagOff == - 1 )
238+ int tagOffset = FindPattern ( buffer , pattern ) ;
239+ if ( tagOffset == - 1 )
232240 return null ;
233241
234242 // Extract string value
235- int searchStart = tagOff + 16 ;
236- int searchEnd = Math . Min ( searchStart + PropertyTagWindow , size ) ;
243+ int valueSearchStart = tagOffset + PatternLength ;
244+ int valueSearchEnd = Math . Min ( valueSearchStart + PropertyTagWindow , fileSize ) ;
237245
238- for ( int i = searchStart ; i < searchEnd - 4 ; i ++ )
246+ for ( int i = valueSearchStart ; i < valueSearchEnd - 4 ; i ++ )
239247 {
240- int pLen = ReadInt32 ( data , i ) ;
248+ int stringLength = ReadInt32 ( buffer , i ) ;
241249
242- if ( pLen > 0 && pLen < 128 )
250+ if ( stringLength > 0 && stringLength < 128 )
243251 {
244- int strEnd = i + 4 + pLen - 1 ; // -1 for null terminator
245- if ( strEnd <= searchEnd )
252+ int stringEnd = i + 4 + stringLength - 1 ; // -1 for null terminator
253+ if ( stringEnd <= valueSearchEnd )
246254 {
247255 // Check if it's printable ASCII
248256 bool valid = true ;
249- for ( int j = i + 4 ; j < strEnd && valid ; j ++ )
257+ for ( int j = i + 4 ; j < stringEnd && valid ; j ++ )
250258 {
251- byte b = data [ j ] ;
259+ byte b = buffer [ j ] ;
252260 if ( b < 32 || b > 126 )
253261 valid = false ;
254262 }
255263
256- if ( valid && strEnd > i + 4 )
264+ if ( valid && stringEnd > i + 4 )
257265 {
258- string value = Encoding . ASCII . GetString ( data , i + 4 , strEnd - i - 4 ) ;
266+ string value = Encoding . ASCII . GetString ( buffer , i + 4 , stringEnd - i - 4 ) ;
259267 return new DecodeResult ( labelType , value ) ;
260268 }
261269 }
262270 }
263- else if ( pLen < 0 && pLen > - 128 )
271+ else if ( stringLength < 0 && stringLength > - 128 )
264272 {
265273 // UTF-16 string
266- int strEnd = i + 4 + ( ( - pLen ) * 2 ) - 2 ; // -2 for null terminator
267- if ( strEnd <= searchEnd && strEnd > i + 4 )
274+ int stringEnd = i + 4 + ( ( - stringLength ) * 2 ) - 2 ; // -2 for null terminator
275+ if ( stringEnd <= valueSearchEnd && stringEnd > i + 4 )
268276 {
269277 try
270278 {
271- string value = Encoding . Unicode . GetString ( data , i + 4 , strEnd - i - 4 ) ;
279+ string value = Encoding . Unicode . GetString ( buffer , i + 4 , stringEnd - i - 4 ) ;
272280 return new DecodeResult ( labelType , value ) ;
273281 }
274282 catch
0 commit comments