diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 63cdd0b852..c91264c03a 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -212,7 +212,7 @@ namespace osu.Game.Beatmaps var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); // metadata may have changed; update the path with the standard format. - beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu"); + beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index 9f00d21383..2274da0fd4 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; @@ -124,5 +125,21 @@ namespace osu.Game.Extensions return instance.OnlineID.Equals(other.OnlineID); } + + private static readonly char[] invalid_filename_characters = Path.GetInvalidFileNameChars() + // Backslash is added to avoid issues when exporting to zip. + // See SharpCompress filename normalisation https://github.com/adamhathcock/sharpcompress/blob/a1e7c0068db814c9aa78d86a94ccd1c761af74bd/src/SharpCompress/Writers/Zip/ZipWriter.cs#L143. + .Append('\\') + .ToArray(); + + /// + /// Get a valid filename for use inside a zip file. Avoids backslashes being incorrectly converted to directories. + /// + public static string GetValidArchiveContentFilename(this string filename) + { + foreach (char c in invalid_filename_characters) + filename = filename.Replace(c, '_'); + return filename; + } } }