2019-01-24 16:43:03 +08:00
|
|
|
|
// 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.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-06-17 15:37:17 +08:00
|
|
|
|
#nullable disable
|
|
|
|
|
|
2022-05-30 16:42:27 +08:00
|
|
|
|
using System.Buffers;
|
2017-07-26 19:22:02 +08:00
|
|
|
|
using System.Collections.Generic;
|
2016-10-05 04:29:08 +08:00
|
|
|
|
using System.IO;
|
2016-10-05 05:08:43 +08:00
|
|
|
|
using System.Linq;
|
2024-04-29 18:49:17 +08:00
|
|
|
|
using System.Text;
|
2022-05-30 16:42:27 +08:00
|
|
|
|
using Microsoft.Toolkit.HighPerformance;
|
2019-10-30 18:33:54 +08:00
|
|
|
|
using osu.Framework.IO.Stores;
|
2017-11-19 12:46:51 +08:00
|
|
|
|
using SharpCompress.Archives.Zip;
|
2024-04-29 18:49:17 +08:00
|
|
|
|
using SharpCompress.Common;
|
|
|
|
|
using SharpCompress.Readers;
|
2022-05-30 16:42:27 +08:00
|
|
|
|
using SixLabors.ImageSharp.Memory;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-02-15 11:56:22 +08:00
|
|
|
|
namespace osu.Game.IO.Archives
|
2016-10-14 11:33:58 +08:00
|
|
|
|
{
|
2018-02-15 11:56:22 +08:00
|
|
|
|
public sealed class ZipArchiveReader : ArchiveReader
|
2016-10-14 11:33:58 +08:00
|
|
|
|
{
|
2024-04-29 18:49:17 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Archives created by osu!stable still write out as Shift-JIS.
|
|
|
|
|
/// We want to force this fallback rather than leave it up to the library/system.
|
|
|
|
|
/// In the future we may want to change exports to set the zip UTF-8 flag and use that instead.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static readonly ArchiveEncoding DEFAULT_ENCODING;
|
|
|
|
|
|
2017-03-23 12:41:50 +08:00
|
|
|
|
private readonly Stream archiveStream;
|
2017-11-19 12:46:51 +08:00
|
|
|
|
private readonly ZipArchive archive;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2024-04-29 18:49:17 +08:00
|
|
|
|
static ZipArchiveReader()
|
|
|
|
|
{
|
|
|
|
|
// Required to support rare code pages.
|
|
|
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
|
|
|
|
|
|
|
|
DEFAULT_ENCODING = new ArchiveEncoding(Encoding.GetEncoding(932), Encoding.GetEncoding(932));
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-15 11:56:22 +08:00
|
|
|
|
public ZipArchiveReader(Stream archiveStream, string name = null)
|
2018-02-15 09:20:23 +08:00
|
|
|
|
: base(name)
|
2016-10-14 11:33:58 +08:00
|
|
|
|
{
|
2016-10-19 23:00:11 +08:00
|
|
|
|
this.archiveStream = archiveStream;
|
2024-04-29 18:49:17 +08:00
|
|
|
|
|
|
|
|
|
archive = ZipArchive.Open(archiveStream, new ReaderOptions
|
|
|
|
|
{
|
|
|
|
|
ArchiveEncoding = DEFAULT_ENCODING
|
|
|
|
|
});
|
2016-10-05 04:29:08 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-11-05 19:00:14 +08:00
|
|
|
|
public override Stream GetStream(string name)
|
2016-10-05 04:29:08 +08:00
|
|
|
|
{
|
2017-11-19 12:46:51 +08:00
|
|
|
|
ZipArchiveEntry entry = archive.Entries.SingleOrDefault(e => e.Key == name);
|
2016-10-14 11:33:58 +08:00
|
|
|
|
if (entry == null)
|
2023-08-03 08:01:11 +08:00
|
|
|
|
return null;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-05-30 16:42:27 +08:00
|
|
|
|
var owner = MemoryAllocator.Default.Allocate<byte>((int)entry.Size);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2017-11-19 12:46:51 +08:00
|
|
|
|
using (Stream s = entry.OpenEntryStream())
|
2024-03-06 10:42:20 +08:00
|
|
|
|
s.ReadExactly(owner.Memory.Span);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-05-30 16:42:27 +08:00
|
|
|
|
return new MemoryOwnerMemoryStream(owner);
|
2016-10-05 04:29:08 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2016-10-19 01:35:01 +08:00
|
|
|
|
public override void Dispose()
|
2016-10-14 11:33:58 +08:00
|
|
|
|
{
|
|
|
|
|
archive.Dispose();
|
2016-10-19 23:00:11 +08:00
|
|
|
|
archiveStream.Dispose();
|
2016-10-10 21:26:34 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
Fix exports containing zero byte files after import from specific ZIP archives
Closes https://github.com/ppy/osu/issues/27540.
As it turns out, some ZIP archivers (like 7zip) will decide to add fake
entries for directories, and some (like windows zipfolders) won't.
The "directory" entries aren't really properly supported using any
actual data or attributes, they're detected by sharpcompress basically
by heuristics:
https://github.com/adamhathcock/sharpcompress/blob/ab5535eba365ec8fae58f92d53763ddf2dbf45af/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs#L19-L31
When importing into realm we have thus far presumed that these directory
entries will not be a thing. Having them be a thing breaks multiple
things, like:
- When importing from ZIPs with separate directory entries, a separate
`RealmFile` is created for a directory entry even though it doesn't
represent a real file
- As a result, when re-exporting a model with files imported from such
an archive, a zero-byte file would be created because to the database
it looks like it was originally a zero-byte file.
If you want to have fun, google "zip empty directories". You'll see
a whole gamut of languages, libraries, and developers stepping on this
rake. Yet another episode of underspecced mistakes from decades ago
that were somebody's "good idea" but continue to wreak havoc forevermore
because now there are two competing conventions you can't just pick one.
2024-03-12 16:06:24 +08:00
|
|
|
|
public override IEnumerable<string> Filenames => archive.Entries.Where(e => !e.IsDirectory).Select(e => e.Key).ExcludeSystemFileNames();
|
2022-05-30 16:42:27 +08:00
|
|
|
|
|
|
|
|
|
private class MemoryOwnerMemoryStream : Stream
|
|
|
|
|
{
|
|
|
|
|
private readonly IMemoryOwner<byte> owner;
|
|
|
|
|
private readonly Stream stream;
|
|
|
|
|
|
|
|
|
|
public MemoryOwnerMemoryStream(IMemoryOwner<byte> owner)
|
|
|
|
|
{
|
|
|
|
|
this.owner = owner;
|
|
|
|
|
|
|
|
|
|
stream = owner.Memory.AsStream();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
owner?.Dispose();
|
|
|
|
|
base.Dispose(disposing);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Flush() => stream.Flush();
|
|
|
|
|
|
|
|
|
|
public override int Read(byte[] buffer, int offset, int count) => stream.Read(buffer, offset, count);
|
|
|
|
|
|
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin);
|
|
|
|
|
|
|
|
|
|
public override void SetLength(long value) => stream.SetLength(value);
|
|
|
|
|
|
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) => stream.Write(buffer, offset, count);
|
|
|
|
|
|
|
|
|
|
public override bool CanRead => stream.CanRead;
|
|
|
|
|
|
|
|
|
|
public override bool CanSeek => stream.CanSeek;
|
|
|
|
|
|
|
|
|
|
public override bool CanWrite => stream.CanWrite;
|
|
|
|
|
|
|
|
|
|
public override long Length => stream.Length;
|
|
|
|
|
|
|
|
|
|
public override long Position
|
|
|
|
|
{
|
|
|
|
|
get => stream.Position;
|
|
|
|
|
set => stream.Position = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-10-14 11:33:58 +08:00
|
|
|
|
}
|
2017-11-19 12:46:51 +08:00
|
|
|
|
}
|