mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 16:13:33 +08:00
95233fc638
Resolves https://github.com/ppy/osu/discussions/36107. The replay linked in the aforementioned discussion happens to contain the magic sequence for ZIP headers in the binary stream (50 4b 05 06) which led to it getting classified as a ZIP with no entries and completely bogus everything in the header at https://github.com/ppy/osu/blob/c8b18acd4dd1d98170a069240b6666cea3d5da07/osu.Game/Database/ImportTask.cs#L51-L56 and then finally dying of 2025-12-23 07:26:45 [error]: [?????] Model creation of replay-osu_2040232_4828629841.osr failed. 2025-12-23 07:26:45 [error]: System.InvalidOperationException: Sequence contains no matching element 2025-12-23 07:26:45 [error]: at System.Linq.ThrowHelper.ThrowNoMatchException() 2025-12-23 07:26:45 [error]: at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate) 2025-12-23 07:26:45 [error]: at osu.Game.Scoring.ScoreImporter.CreateModel(ArchiveReader archive, ImportParameters parameters) 2025-12-23 07:26:45 [error]: at osu.Game.Database.RealmArchiveModelImporter`1.importFromArchive(ArchiveReader archive, ImportParameters parameters, CancellationToken cancellationToken) at https://github.com/ppy/osu/blob/554961036e23615b35eb6b66b341b10924664c4d/osu.Game/Scoring/ScoreImporter.cs#L46
78 lines
2.9 KiB
C#
78 lines
2.9 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.IO;
|
|
using SharpCompress.Archives.Zip;
|
|
|
|
namespace osu.Game.Utils
|
|
{
|
|
public static class ZipUtils
|
|
{
|
|
public static bool IsZipArchive(MemoryStream stream)
|
|
{
|
|
try
|
|
{
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
using (var arc = ZipArchive.Open(stream))
|
|
{
|
|
foreach (var entry in arc.Entries)
|
|
{
|
|
using (entry.OpenEntryStream())
|
|
{
|
|
}
|
|
}
|
|
|
|
// aside from opening every zip entry not failing, we also require there to *be* at least one entry.
|
|
// if there are no entries, the best case is that it's an actual empty zip
|
|
// and as such probably useless to whatever wants to use it later.
|
|
// the worst case is that it's actually *not* a zip and instead a stream of binary
|
|
// which *accidentally* happened to contain the magic sequence of bytes for the zip header (50 4b 05 06),
|
|
// and if that's the case, then we are *misclassifying* it as a zip by returning `true` unconditionally.
|
|
return arc.Entries.Count > 0;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
}
|
|
}
|
|
|
|
public static bool IsZipArchive(string path)
|
|
{
|
|
if (!File.Exists(path))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
using (var arc = ZipArchive.Open(path))
|
|
{
|
|
foreach (var entry in arc.Entries)
|
|
{
|
|
using (entry.OpenEntryStream())
|
|
{
|
|
}
|
|
}
|
|
|
|
// aside from opening every zip entry not failing, we also require there to *be* at least one entry.
|
|
// if there are no entries, the best case is that it's an actual empty zip
|
|
// and as such probably useless to whatever wants to use it later.
|
|
// the worst case is that it's actually *not* a zip and instead a stream of binary
|
|
// which *accidentally* happened to contain the magic sequence of bytes for the zip header (50 4b 05 06),
|
|
// and if that's the case, then we are *misclassifying* it as a zip by returning `true` unconditionally.
|
|
return arc.Entries.Count > 0;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|