Skip to content

Commit ac3dd96

Browse files
committed
Engine support for finding files on linux systems the way the game would do it wiht abstractions layers like Wine or Valve Proton
1 parent 7679e74 commit ac3dd96

1 file changed

Lines changed: 94 additions & 15 deletions

File tree

src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/GameRepository.Files.cs

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,25 +97,29 @@ protected FileFoundInfo GetFileInfoFromMasterMeg(ReadOnlySpan<char> filePath)
9797
{
9898
Debug.Assert(MasterMegArchive is not null);
9999

100-
if (filePath.Length > PGConstants.MaxMegEntryPathLength)
100+
var sb = new ValueStringBuilder(stackalloc char[Math.Max(filePath.Length, PGConstants.MaxMegEntryPathLength)]);
101+
sb.Append(filePath);
102+
NormalizePath(ref sb);
103+
104+
if (sb.Length > PGConstants.MaxMegEntryPathLength)
101105
{
102-
Logger.LogWarning("Trying to open a MEG entry which is longer than 259 characters: '{FilePath}'", filePath.ToString());
106+
Logger.LogWarning("Trying to open a MEG entry which is longer than 259 characters: '{FileName}'", sb.ToString());
107+
sb.Dispose();
103108
return default;
104109
}
105110

106111
Span<char> fileNameSpan = stackalloc char[PGConstants.MaxMegEntryPathLength];
107112

108-
if (!_megPathNormalizer.TryNormalize(filePath, fileNameSpan, out var length))
109-
return default;
110-
111-
var fileName = fileNameSpan.Slice(0, length);
112-
113-
if (fileName.Length > PGConstants.MaxMegEntryPathLength)
113+
if (!_megPathNormalizer.TryNormalize(sb.AsSpan(), fileNameSpan, out var length))
114114
{
115-
Logger.LogWarning("Trying to open a MEG entry which is longer than 259 characters after normalization: '{FileName}'", fileName.ToString());
115+
sb.Dispose();
116116
return default;
117117
}
118118

119+
sb.Dispose();
120+
121+
var fileName = fileNameSpan.Slice(0, length);
122+
119123
var crc = _crc32HashingService.GetCrc32(fileName, MegFileConstants.MegDataEntryPathEncoding);
120124

121125
var entry = MasterMegArchive!.EntriesWithCrc(crc).FirstOrDefault();
@@ -133,13 +137,15 @@ protected FileFoundInfo FindFileCore(ReadOnlySpan<char> filePath, ref ValueStrin
133137
stringBuilder.Append(filePath);
134138
else
135139
FileSystem.Path.Join(GameDirectory.AsSpan(), filePath, ref stringBuilder);
140+
136141

137-
var actualFilePath = stringBuilder.AsSpan();
138-
139-
// We accept a *possible* difference here between platforms,
140-
// unless it's proven the differences are too significant.
141142
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
142-
exists = FileSystem.File.Exists(actualFilePath.ToString());
143+
{
144+
NormalizePath(ref stringBuilder);
145+
146+
var actualFilePath = stringBuilder.AsSpan();
147+
exists = FileSystemPathExistsCaseInsensitive(actualFilePath, ref stringBuilder);
148+
}
143149
else
144150
{
145151
// We *could* also use the slightly faster GetFileAttributesA.
@@ -157,7 +163,7 @@ in stringBuilder.GetPinnableReference(true),
157163

158164
exists = IsValidAndClose(fileHandle);
159165
}
160-
return !exists ? new FileFoundInfo() : new FileFoundInfo(actualFilePath);
166+
return !exists ? new FileFoundInfo() : new FileFoundInfo(stringBuilder.AsSpan());
161167
}
162168

163169
protected FileFoundInfo FileFromAltExists(ReadOnlySpan<char> filePath, IList<string> fallbackPaths, ref ValueStringBuilder pathStringBuilder)
@@ -183,6 +189,79 @@ protected FileFoundInfo FileFromAltExists(ReadOnlySpan<char> filePath, IList<str
183189

184190
return default;
185191
}
192+
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+
}
186265

187266
private static bool PathStartsWithDataDirectory(ReadOnlySpan<char> path, out int cutoffLength)
188267
{

0 commit comments

Comments
 (0)