Skip to content

Commit c221577

Browse files
committed
find file from alternate matches better the engine behavior
1 parent 4d8b4d5 commit c221577

8 files changed

Lines changed: 172 additions & 91 deletions

File tree

src/ModVerify/Reporting/Engine/XmlParseErrorReporter.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO.Abstractions;
4-
using System.Xml;
54
using AET.ModVerify.Utilities;
65
using AET.ModVerify.Verifiers;
76
using Microsoft.Extensions.DependencyInjection;

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem.Test/IO/PetroglyphFileSystemTests.CombineJoin.cs

Lines changed: 107 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using PG.StarWarsGame.Engine.IO;
34
using PG.StarWarsGame.Engine.Utilities;
45
using Xunit;
56
#if NETFRAMEWORK
@@ -10,34 +11,37 @@ namespace PG.StarWarsGame.Engine.FileSystem.Test.IO;
1011

1112
public partial class PetroglyphFileSystemTests
1213
{
14+
private static string Sep = PetroglyphFileSystem.DirectorySeparatorChar.ToString();
15+
private static string AltSep = PetroglyphFileSystem.AltDirectorySeparatorChar.ToString();
16+
private static string OsSep = Path.DirectorySeparatorChar.ToString();
17+
18+
// Mirrors TestData_JoinTwoPaths, but with CombinePath semantics: a rooted second path replaces the
19+
// first entirely. Null cases are omitted because CombinePath throws on null (see the dedicated facts).
20+
public static TheoryData<string, string, string> TestData_CombineTwoPaths = new()
21+
{
22+
{ "", "", "" },
23+
{ Sep, "", Sep },
24+
{ AltSep, "", AltSep },
25+
{ "", Sep, Sep },
26+
{ "", AltSep, AltSep },
27+
{ Sep, Sep, Sep },
28+
{ AltSep, AltSep, AltSep },
29+
{ "a", "", "a" },
30+
{ "", "a", "a" },
31+
{ "a", "a", $"a{OsSep}a" },
32+
{ $"a{Sep}", "a", $"a{Sep}a" },
33+
{ "a", $"{Sep}a", $"{Sep}a" },
34+
{ $"a{Sep}", $"{Sep}a", $"{Sep}a" },
35+
{ "a", $"a{Sep}", $"a{OsSep}a{Sep}" },
36+
{ $"a{AltSep}", "a", $"a{AltSep}a" },
37+
{ "a", $"{AltSep}a", $"{AltSep}a" },
38+
{ $"a{Sep}", $"{AltSep}a", $"{AltSep}a" },
39+
{ $"a{AltSep}", $"{AltSep}a", $"{AltSep}a" },
40+
{ "a", $"a{AltSep}", $"a{OsSep}a{AltSep}" }
41+
};
42+
1343
[Theory]
14-
#if Windows
15-
[InlineData("a", "b", "a\\b")]
16-
[InlineData("a/", "b", "a/b")]
17-
[InlineData("a\\", "b", "a\\b")]
18-
[InlineData("", "b", "b")]
19-
[InlineData("a", "", "a")]
20-
[InlineData("/", "b", "/b")]
21-
[InlineData("a", "/b", "/b")]
22-
[InlineData("a", "\\b", "\\b")]
23-
[InlineData("a/b", "c/d", "a/b\\c/d")]
24-
[InlineData("a\\b", "c\\d", "a\\b\\c\\d")]
25-
[InlineData("a/b/", "c/d", "a/b/c/d")]
26-
[InlineData("a\\b\\", "c\\d", "a\\b\\c\\d")]
27-
#else
28-
[InlineData("a", "b", "a/b")]
29-
[InlineData("a/", "b", "a/b")]
30-
[InlineData("a\\", "b", "a\\b")]
31-
[InlineData("", "b", "b")]
32-
[InlineData("a", "", "a")]
33-
[InlineData("/", "b", "/b")]
34-
[InlineData("a", "/b", "/b")]
35-
[InlineData("a", "\\b", "\\b")]
36-
[InlineData("a/b", "c/d", "a/b/c/d")]
37-
[InlineData("a\\b", "c\\d", "a\\b/c\\d")]
38-
[InlineData("a/b/", "c/d", "a/b/c/d")]
39-
[InlineData("a\\b\\", "c\\d", "a\\b\\c\\d")]
40-
#endif
44+
[MemberData(nameof(TestData_CombineTwoPaths))]
4145
public void CombinePath(string pathA, string pathB, string expected)
4246
{
4347
var result = _pgFileSystem.CombinePath(pathA, pathB);
@@ -47,31 +51,35 @@ public void CombinePath(string pathA, string pathB, string expected)
4751
#endif
4852
}
4953

54+
public static TheoryData<string?, string?, string> TestData_JoinTwoPaths = new()
55+
{
56+
{ "", "", "" },
57+
{ Sep, "", Sep },
58+
{ AltSep, "", AltSep },
59+
{ "", Sep, Sep },
60+
{ "", AltSep, AltSep },
61+
{ Sep, Sep, $"{Sep}{Sep}" },
62+
{ AltSep, AltSep, $"{AltSep}{AltSep}" },
63+
{ "a", "", "a" },
64+
{ "", "a", "a" },
65+
{ "a", "a", $"a{OsSep}a" },
66+
{ $"a{Sep}", "a", $"a{Sep}a" },
67+
{ "a", $"{Sep}a", $"a{Sep}a" },
68+
{ $"a{Sep}", $"{Sep}a", $"a{Sep}{Sep}a" },
69+
{ "a", $"a{Sep}", $"a{OsSep}a{Sep}" },
70+
{ $"a{AltSep}", "a", $"a{AltSep}a" },
71+
{ "a", $"{AltSep}a", $"a{AltSep}a" },
72+
{ $"a{Sep}", $"{AltSep}a", $"a{Sep}{AltSep}a" },
73+
{ $"a{AltSep}", $"{AltSep}a", $"a{AltSep}{AltSep}a" },
74+
{ "a", $"a{AltSep}", $"a{OsSep}a{AltSep}" },
75+
{ null, null, ""},
76+
{ null, "a", "a"},
77+
{ "a", null, "a"}
78+
};
79+
5080
[Theory]
51-
#if Windows
52-
[InlineData("a", "b", "a\\b")]
53-
[InlineData("a/", "b", "a/b")]
54-
[InlineData("a\\", "b", "a\\b")]
55-
[InlineData("", "b", "b")]
56-
[InlineData("a", "", "a")]
57-
[InlineData("/", "b", "/b")]
58-
[InlineData("a", "/b", "a/b")]
59-
[InlineData("a", "\\b", "a\\b")]
60-
[InlineData("a/b", "c/d", "a/b\\c/d")]
61-
[InlineData("a\\b", "c\\d", "a\\b\\c\\d")]
62-
#else
63-
[InlineData("a", "b", "a/b")]
64-
[InlineData("a/", "b", "a/b")]
65-
[InlineData("a\\", "b", "a\\b")]
66-
[InlineData("", "b", "b")]
67-
[InlineData("a", "", "a")]
68-
[InlineData("/", "b", "/b")]
69-
[InlineData("a", "/b", "a/b")]
70-
[InlineData("a", "\\b", "a\\b")]
71-
[InlineData("a/b", "c/d", "a/b/c/d")]
72-
[InlineData("a\\b", "c\\d", "a\\b/c\\d")]
73-
#endif
74-
public void JoinPath(string path1, string path2, string expected)
81+
[MemberData(nameof(TestData_JoinTwoPaths))]
82+
public void JoinPath(string? path1, string? path2, string expected)
7583
{
7684
var vsb = new ValueStringBuilder();
7785
try
@@ -89,6 +97,54 @@ public void JoinPath(string path1, string path2, string expected)
8997
}
9098
}
9199

100+
public static TheoryData<string?, string?, string?, string> TestData_JoinThreePaths = new()
101+
{
102+
{ "", "", "", "" },
103+
{ Sep, Sep, Sep, $"{Sep}{Sep}{Sep}" },
104+
{ AltSep, AltSep, AltSep, $"{AltSep}{AltSep}{AltSep}" },
105+
{ "a", "", "", "a" },
106+
{ "", "a", "", "a" },
107+
{ "", "", "a", "a" },
108+
{ "a", "", "a", $"a{OsSep}a" },
109+
{ "a", "a", "", $"a{OsSep}a" },
110+
{ "", "a", "a", $"a{OsSep}a" },
111+
{ "a", "a", "a", $"a{OsSep}a{OsSep}a" },
112+
{ "a", Sep, "a", $"a{Sep}a" },
113+
{ $"a{Sep}", "", "a", $"a{Sep}a" },
114+
{ $"a{Sep}", "a", "", $"a{Sep}a" },
115+
{ "", $"a{Sep}", "a", $"a{Sep}a" },
116+
{ "a", "", $"{Sep}a", $"a{Sep}a" },
117+
{ $"a{AltSep}", "", "a", $"a{AltSep}a" },
118+
{ $"a{AltSep}", "a", "", $"a{AltSep}a" },
119+
{ "", $"a{AltSep}", "a", $"a{AltSep}a" },
120+
{ "a", "", $"{AltSep}a", $"a{AltSep}a" },
121+
{ null, null, null, "" },
122+
{ "a", null, null, "a" },
123+
{ null, "a", null, "a" },
124+
{ null, null, "a", "a" },
125+
{ "a", null, "a", $"a{OsSep}a" }
126+
};
127+
128+
[Theory]
129+
[MemberData(nameof(TestData_JoinThreePaths))]
130+
public void JoinPath_ThreePaths(string? path1, string? path2, string? path3, string expected)
131+
{
132+
var vsb = new ValueStringBuilder();
133+
try
134+
{
135+
_pgFileSystem.JoinPath(path1.AsSpan(), path2.AsSpan(), path3.AsSpan(), ref vsb);
136+
var result = vsb.ToString();
137+
Assert.Equal(expected, result);
138+
#if Windows
139+
Assert.Equal(result, _fileSystem.Path.Join(path1, path2, path3));
140+
#endif
141+
}
142+
finally
143+
{
144+
vsb.Dispose();
145+
}
146+
}
147+
92148
[Fact]
93149
public void CombinePath_FirstArgNull_Throws()
94150
{

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/PetroglyphFileSystem.CombineJoin.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public string CombinePath(string pathA, string pathB)
3838
throw new ArgumentNullException(nameof(pathB));
3939
return CombineInternal(pathA, pathB);
4040
}
41-
41+
4242
internal void JoinPath(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ref ValueStringBuilder stringBuilder)
4343
{
4444
if (path1.Length == 0 && path2.Length == 0)
@@ -59,7 +59,44 @@ internal void JoinPath(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ref V
5959

6060
stringBuilder.Append(path2);
6161
}
62-
62+
63+
internal void JoinPath(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, ref ValueStringBuilder stringBuilder)
64+
{
65+
if (path1.IsEmpty)
66+
{
67+
JoinPath(path2, path3, ref stringBuilder);
68+
return;
69+
}
70+
71+
if (path2.IsEmpty)
72+
{
73+
JoinPath(path1, path3, ref stringBuilder);
74+
return;
75+
}
76+
77+
if (path3.IsEmpty)
78+
{
79+
JoinPath(path1, path2, ref stringBuilder);
80+
return;
81+
}
82+
83+
stringBuilder.Append(path1);
84+
85+
var firstHasSeparator = IsDirectorySeparator(path1[path1.Length - 1]) || IsDirectorySeparator(path2[0]);
86+
var secondHasSeparator = IsDirectorySeparator(path2[path2.Length - 1]) || IsDirectorySeparator(path3[0]);
87+
88+
if (!firstHasSeparator)
89+
stringBuilder.Append(UnderlyingFileSystem.Path.DirectorySeparatorChar);
90+
91+
stringBuilder.Append(path2);
92+
93+
if (!secondHasSeparator)
94+
stringBuilder.Append(UnderlyingFileSystem.Path.DirectorySeparatorChar);
95+
96+
stringBuilder.Append(path3);
97+
}
98+
99+
63100
private string CombineInternal(string first, string second)
64101
{
65102
if (string.IsNullOrEmpty(first))

src/PetroglyphTools/PG.StarWarsGame.Engine.FileSystem/IO/PetroglyphFileSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ namespace PG.StarWarsGame.Engine.IO;
1616
/// </remarks>
1717
public sealed partial class PetroglyphFileSystem
1818
{
19-
private const char DirectorySeparatorChar = '/';
20-
private const char AltDirectorySeparatorChar = '\\';
19+
internal const char DirectorySeparatorChar = '/';
20+
internal const char AltDirectorySeparatorChar = '\\';
2121

2222
// ReSharper disable once InconsistentNaming
2323
private static readonly PathNormalizeOptions PGFileSystemDirectorySeparatorNormalizeOptions = new()

src/PetroglyphTools/PG.StarWarsGame.Engine/IO/IGameRepository.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
using PG.StarWarsGame.Engine.Localization;
1+
using PG.StarWarsGame.Engine.Localization;
22

33
namespace PG.StarWarsGame.Engine.IO;
44

55
public interface IGameRepository : IRepository
66
{
77
/// <summary>
8-
/// Gets the full qualified path of this repository with a trailing directory separator
8+
/// Gets the fully qualified path of the repository's top-most root — the first mod directory when mods
9+
/// are configured, otherwise the base game directory — with a trailing directory separator.
910
/// </summary>
1011
string Path { get; }
1112

@@ -20,7 +21,5 @@ public interface IGameRepository : IRepository
2021

2122
IRepository ModelRepository { get; }
2223

23-
bool FileExists(string filePath, string[] extensions, bool megFileOnly = false);
24-
2524
bool IsLanguageInstalled(LanguageType languageType);
2625
}

src/PetroglyphTools/PG.StarWarsGame.Engine/IO/Repositories/FocGameRepository.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ public FocGameRepository(GameLocations gameLocations, GameEngineErrorReporterWra
2525
if (firstFallback is not null)
2626
{
2727
var eawMegs = LoadMegArchivesFromXml(firstFallback);
28-
var eawPatch = LoadMegArchive(PGFileSystem.UnderlyingFileSystem.Path.Combine(firstFallback, "Data/Patch.meg"));
29-
var eawPatch2 = LoadMegArchive(PGFileSystem.UnderlyingFileSystem.Path.Combine(firstFallback, "Data/Patch2.meg"));
30-
var eaw64Patch = LoadMegArchive(PGFileSystem.UnderlyingFileSystem.Path.Combine(firstFallback, "Data/64Patch.meg"));
28+
var eawPatch = LoadMegArchive(PGFileSystem.UnderlyingFileSystem.Path.Combine(firstFallback, "Data\\Patch.meg"));
29+
var eawPatch2 = LoadMegArchive(PGFileSystem.UnderlyingFileSystem.Path.Combine(firstFallback, "Data\\Patch2.meg"));
30+
var eaw64Patch = LoadMegArchive(PGFileSystem.UnderlyingFileSystem.Path.Combine(firstFallback, "Data\\64Patch.meg"));
3131

3232
megsToConsider.AddRange(eawMegs);
3333
if (eawPatch is not null)
@@ -39,9 +39,9 @@ public FocGameRepository(GameLocations gameLocations, GameEngineErrorReporterWra
3939
}
4040

4141
var focOrModMegs = LoadMegArchivesFromXml(".");
42-
var focPatch = LoadMegArchive("Data/Patch.meg");
43-
var focPatch2 = LoadMegArchive("Data/Patch2.meg");
44-
var foc64Patch = LoadMegArchive("Data/64Patch.meg");
42+
var focPatch = LoadMegArchive("Data\\Patch.meg");
43+
var focPatch2 = LoadMegArchive("Data\\Patch2.meg");
44+
var foc64Patch = LoadMegArchive("Data\\64Patch.meg");
4545

4646
megsToConsider.AddRange(focOrModMegs);
4747
if (focPatch is not null)

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

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,6 @@ internal partial class GameRepository
1313
{
1414
private static readonly string[] DataPathPrefixes = ["DATA/", "DATA\\", "./DATA/", ".\\DATA\\"];
1515

16-
public bool FileExists(string filePath, string[] extensions, bool megFileOnly = false)
17-
{
18-
foreach (var extension in extensions)
19-
{
20-
var newPath = PGFileSystem.ChangeExtension(filePath, extension);
21-
if (FileExists(newPath, megFileOnly))
22-
return true;
23-
}
24-
return false;
25-
}
26-
2716
public bool FileExists(string filePath, bool megFileOnly = false)
2817
{
2918
return FileExists(filePath.AsSpan(), megFileOnly);
@@ -172,7 +161,7 @@ protected FileFoundInfo FileFromAltExists(ReadOnlySpan<char> filePath, IList<str
172161
{
173162
pathStringBuilder.Length = 0;
174163

175-
PGFileSystem.JoinPath(fallbackPath.AsSpan(), pathWithNormalizedData, ref pathStringBuilder);
164+
PGFileSystem.JoinPath(fallbackPath.AsSpan(), "Data", pathWithNormalizedData, ref pathStringBuilder);
176165
var newPath = pathStringBuilder.AsSpan();
177166

178167
var fileFoundInfo = FindFileCore(newPath, ref pathStringBuilder, fallbackPath.AsSpan());
@@ -185,24 +174,25 @@ protected FileFoundInfo FileFromAltExists(ReadOnlySpan<char> filePath, IList<str
185174

186175
private bool PathStartsWithDataDirectory(ReadOnlySpan<char> path, out int cutoffLength)
187176
{
188-
cutoffLength = 0;
189-
if (path.Length < 5)
190-
return false;
191-
192177
var sb = new ValueStringBuilder(stackalloc char[265]);
193178
sb.Append(path);
179+
180+
// Normalizing is necessary because a previous Join/Combine uses the systems directory separator,
181+
// while hardcoded paths usually use backslashes. This might lead to "asymmetric" separator usage.
182+
// DataPathPrefixes paths only cover symmetric cases.
194183
PGFileSystem.NormalizePath(ref sb);
195184
try
196185
{
197186
foreach (var prefix in DataPathPrefixes)
198187
{
199188
if (sb.AsSpan().StartsWith(prefix.AsSpan(), StringComparison.OrdinalIgnoreCase))
200189
{
201-
if (path[0] == '.')
202-
cutoffLength = 2;
190+
cutoffLength = prefix.Length;
203191
return true;
204192
}
205193
}
194+
195+
cutoffLength = 0;
206196
return false;
207197
}
208198
finally

0 commit comments

Comments
 (0)