mirror of
https://github.com/ppy/osu.git
synced 2024-11-14 15:17:27 +08:00
Merge branch 'master' into preserve-storyboard
This commit is contained in:
commit
72b59c01f7
BIN
osu.Game.Tests/Resources/Archives/japanese-filename.osz
Normal file
BIN
osu.Game.Tests/Resources/Archives/japanese-filename.osz
Normal file
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
// 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.IO;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
@ -12,6 +13,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual;
|
||||
using MemoryStream = System.IO.MemoryStream;
|
||||
|
||||
namespace osu.Game.Tests.Skins
|
||||
{
|
||||
@ -21,6 +23,52 @@ namespace osu.Game.Tests.Skins
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveAndLegacyExportJapaneseFilename()
|
||||
{
|
||||
IWorkingBeatmap beatmap = null!;
|
||||
MemoryStream outStream = null!;
|
||||
|
||||
// Ensure importer encoding is correct
|
||||
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"japanese-filename.osz"));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
|
||||
// Ensure exporter encoding is correct (round trip)
|
||||
AddStep("export", () =>
|
||||
{
|
||||
outStream = new MemoryStream();
|
||||
|
||||
new LegacyBeatmapExporter(LocalStorage)
|
||||
.ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null);
|
||||
});
|
||||
|
||||
AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveAndNonLegacyExportJapaneseFilename()
|
||||
{
|
||||
IWorkingBeatmap beatmap = null!;
|
||||
MemoryStream outStream = null!;
|
||||
|
||||
// Ensure importer encoding is correct
|
||||
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"japanese-filename.osz"));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
|
||||
// Ensure exporter encoding is correct (round trip)
|
||||
AddStep("export", () =>
|
||||
{
|
||||
outStream = new MemoryStream();
|
||||
|
||||
new BeatmapExporter(LocalStorage)
|
||||
.ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null);
|
||||
});
|
||||
|
||||
AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"見本")) != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveOggAudio()
|
||||
{
|
||||
@ -45,6 +93,12 @@ namespace osu.Game.Tests.Skins
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"spinner-osu")) != null);
|
||||
}
|
||||
|
||||
private IWorkingBeatmap importBeatmapFromStream(Stream stream)
|
||||
{
|
||||
var imported = beatmaps.Import(new ImportTask(stream, "filename.osz")).GetResultSafely();
|
||||
return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0]));
|
||||
}
|
||||
|
||||
private IWorkingBeatmap importBeatmapFromArchives(string filename)
|
||||
{
|
||||
var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Database
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool UseFixedEncoding => false;
|
||||
|
||||
protected override string FileExtension => @".olz";
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using Realms;
|
||||
using SharpCompress.Common;
|
||||
@ -22,6 +24,11 @@ namespace osu.Game.Database
|
||||
public abstract class LegacyArchiveExporter<TModel> : LegacyExporter<TModel>
|
||||
where TModel : RealmObject, IHasNamedFiles, IHasGuidPrimaryKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to always use Shift-JIS encoding for archive filenames (like osu!stable did).
|
||||
/// </summary>
|
||||
protected virtual bool UseFixedEncoding => true;
|
||||
|
||||
protected LegacyArchiveExporter(Storage storage)
|
||||
: base(storage)
|
||||
{
|
||||
@ -29,7 +36,12 @@ namespace osu.Game.Database
|
||||
|
||||
public override void ExportToStream(TModel model, Stream outputStream, ProgressNotification? notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var writer = new ZipWriter(outputStream, new ZipWriterOptions(CompressionType.Deflate)))
|
||||
var zipWriterOptions = new ZipWriterOptions(CompressionType.Deflate)
|
||||
{
|
||||
ArchiveEncoding = UseFixedEncoding ? ZipArchiveReader.DEFAULT_ENCODING : new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8)
|
||||
};
|
||||
|
||||
using (var writer = new ZipWriter(outputStream, zipWriterOptions))
|
||||
{
|
||||
int i = 0;
|
||||
int fileCount = model.Files.Count();
|
||||
|
@ -35,6 +35,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Input;
|
||||
using Realms;
|
||||
using Realms.Exceptions;
|
||||
@ -321,12 +322,32 @@ namespace osu.Game.Database
|
||||
{
|
||||
Logger.Error(e, "Your local database is too new to work with this version of osu!. Please close osu! and install the latest release to recover your data.");
|
||||
|
||||
// If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about.
|
||||
// If a newer version database already exists, don't create another backup. We can presume that the first backup is the one we care about.
|
||||
if (!storage.Exists(newerVersionFilename))
|
||||
createBackup(newerVersionFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This error can occur due to file handles still being open by a previous instance.
|
||||
// If this is the case, rather than assuming the realm file is corrupt, block game startup.
|
||||
if (e.Message.StartsWith("SetEndOfFile() failed", StringComparison.Ordinal))
|
||||
{
|
||||
// This will throw if the realm file is not available for write access after 5 seconds.
|
||||
FileUtils.AttemptOperation(() =>
|
||||
{
|
||||
if (storage.Exists(Filename))
|
||||
{
|
||||
using (var _ = storage.GetStream(Filename, FileAccess.ReadWrite))
|
||||
{
|
||||
}
|
||||
}
|
||||
}, 20);
|
||||
|
||||
// If the above eventually succeeds, try and continue startup as per normal.
|
||||
// This may throw again but let's allow it to, and block startup.
|
||||
return getRealmInstance();
|
||||
}
|
||||
|
||||
Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made.");
|
||||
createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}");
|
||||
}
|
||||
@ -1142,33 +1163,18 @@ namespace osu.Game.Database
|
||||
{
|
||||
Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database);
|
||||
|
||||
int attempts = 10;
|
||||
|
||||
while (true)
|
||||
FileUtils.AttemptOperation(() =>
|
||||
{
|
||||
try
|
||||
using (var source = storage.GetStream(Filename, mode: FileMode.Open))
|
||||
{
|
||||
using (var source = storage.GetStream(Filename, mode: FileMode.Open))
|
||||
{
|
||||
// source may not exist.
|
||||
if (source == null)
|
||||
return;
|
||||
// source may not exist.
|
||||
if (source == null)
|
||||
return;
|
||||
|
||||
using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew))
|
||||
source.CopyTo(destination);
|
||||
}
|
||||
|
||||
return;
|
||||
using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew))
|
||||
source.CopyTo(destination);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (attempts-- <= 0)
|
||||
throw;
|
||||
|
||||
// file may be locked during use.
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
}, 20);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -449,16 +449,6 @@ namespace osu.Game.Database
|
||||
return reader.Name.ComputeSHA2Hash();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create all required <see cref="File"/>s for the provided archive, adding them to the global file store.
|
||||
/// </summary>
|
||||
private List<RealmNamedFileUsage> createFileInfos(ArchiveReader reader, RealmFileStore files, Realm realm)
|
||||
{
|
||||
var fileInfos = new List<RealmNamedFileUsage>();
|
||||
|
||||
return fileInfos;
|
||||
}
|
||||
|
||||
private IEnumerable<(string original, string shortened)> getShortenedFilenames(ArchiveReader reader)
|
||||
{
|
||||
string prefix = reader.Filenames.GetCommonPrefix();
|
||||
|
@ -7,23 +7,45 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Toolkit.HighPerformance;
|
||||
using osu.Framework.IO.Stores;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SixLabors.ImageSharp.Memory;
|
||||
|
||||
namespace osu.Game.IO.Archives
|
||||
{
|
||||
public sealed class ZipArchiveReader : ArchiveReader
|
||||
{
|
||||
/// <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;
|
||||
|
||||
private readonly Stream archiveStream;
|
||||
private readonly ZipArchive archive;
|
||||
|
||||
static ZipArchiveReader()
|
||||
{
|
||||
// Required to support rare code pages.
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
DEFAULT_ENCODING = new ArchiveEncoding(Encoding.GetEncoding(932), Encoding.GetEncoding(932));
|
||||
}
|
||||
|
||||
public ZipArchiveReader(Stream archiveStream, string name = null)
|
||||
: base(name)
|
||||
{
|
||||
this.archiveStream = archiveStream;
|
||||
archive = ZipArchive.Open(archiveStream);
|
||||
|
||||
archive = ZipArchive.Open(archiveStream, new ReaderOptions
|
||||
{
|
||||
ArchiveEncoding = DEFAULT_ENCODING
|
||||
});
|
||||
}
|
||||
|
||||
public override Stream GetStream(string name)
|
||||
|
@ -841,7 +841,10 @@ namespace osu.Game
|
||||
{
|
||||
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance).
|
||||
// However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there.
|
||||
{ FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen }
|
||||
{ FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen },
|
||||
{ FrameworkSetting.VolumeUniversal, 0.6 },
|
||||
{ FrameworkSetting.VolumeMusic, 0.6 },
|
||||
{ FrameworkSetting.VolumeEffect, 0.6 },
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,10 +56,6 @@ namespace osu.Game.Screens.Backgrounds
|
||||
introSequence = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence);
|
||||
|
||||
AddInternal(seasonalBackgroundLoader);
|
||||
|
||||
// Load first background asynchronously as part of BDL load.
|
||||
currentDisplay = RNG.Next(0, background_count);
|
||||
Next();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -73,6 +69,9 @@ namespace osu.Game.Screens.Backgrounds
|
||||
introSequence.ValueChanged += _ => Scheduler.AddOnce(next);
|
||||
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(next);
|
||||
|
||||
currentDisplay = RNG.Next(0, background_count);
|
||||
Next();
|
||||
|
||||
// helper function required for AddOnce usage.
|
||||
void next() => Next();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user