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
|
|
|
|
|
2017-12-02 00:43:33 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2020-01-23 23:23:53 +08:00
|
|
|
|
using osu.Framework.Extensions;
|
2018-03-22 11:58:02 +08:00
|
|
|
|
using osu.Framework.Logging;
|
2018-06-28 17:20:43 +08:00
|
|
|
|
using osu.Game.Audio;
|
|
|
|
|
using osu.Game.Beatmaps.ControlPoints;
|
2019-09-10 06:43:30 +08:00
|
|
|
|
using osu.Game.IO;
|
2020-04-14 20:05:07 +08:00
|
|
|
|
using osu.Game.Rulesets.Objects.Legacy;
|
2024-07-23 23:35:25 +08:00
|
|
|
|
using osu.Game.Skinning;
|
2018-11-20 15:51:59 +08:00
|
|
|
|
using osuTK.Graphics;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2017-12-02 00:43:33 +08:00
|
|
|
|
namespace osu.Game.Beatmaps.Formats
|
|
|
|
|
{
|
2018-03-09 20:23:03 +08:00
|
|
|
|
public abstract class LegacyDecoder<T> : Decoder<T>
|
|
|
|
|
where T : new()
|
2017-12-02 00:43:33 +08:00
|
|
|
|
{
|
2020-11-12 16:03:42 +08:00
|
|
|
|
public const int LATEST_VERSION = 14;
|
|
|
|
|
|
2018-03-09 20:23:03 +08:00
|
|
|
|
protected readonly int FormatVersion;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-09 20:23:03 +08:00
|
|
|
|
protected LegacyDecoder(int version)
|
2017-12-02 00:43:33 +08:00
|
|
|
|
{
|
2018-03-09 20:23:03 +08:00
|
|
|
|
FormatVersion = version;
|
2017-12-02 00:43:33 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-09-10 06:43:30 +08:00
|
|
|
|
protected override void ParseStreamInto(LineBufferedReader stream, T output)
|
2017-12-02 00:43:33 +08:00
|
|
|
|
{
|
2021-11-02 12:17:21 +08:00
|
|
|
|
Section section = Section.General;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-07-06 13:29:55 +08:00
|
|
|
|
string? line;
|
2019-04-01 11:16:05 +08:00
|
|
|
|
|
2017-12-02 00:43:33 +08:00
|
|
|
|
while ((line = stream.ReadLine()) != null)
|
|
|
|
|
{
|
2017-12-02 23:05:39 +08:00
|
|
|
|
if (ShouldSkipLine(line))
|
2017-12-02 00:43:33 +08:00
|
|
|
|
continue;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-03-25 12:35:54 +08:00
|
|
|
|
if (section != Section.Metadata)
|
|
|
|
|
{
|
|
|
|
|
// comments should not be stripped from metadata lines, as the song metadata may contain "//" as valid data.
|
|
|
|
|
line = StripComments(line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
line = line.TrimEnd();
|
2021-03-18 14:36:07 +08:00
|
|
|
|
|
2020-02-09 01:05:27 +08:00
|
|
|
|
if (line.StartsWith('[') && line.EndsWith(']'))
|
2017-12-02 00:43:33 +08:00
|
|
|
|
{
|
2019-12-14 20:54:22 +08:00
|
|
|
|
if (!Enum.TryParse(line[1..^1], out section))
|
2019-08-12 00:42:05 +08:00
|
|
|
|
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-03-30 16:18:09 +08:00
|
|
|
|
OnBeginNewSection(section);
|
2017-12-02 00:43:33 +08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-08-07 18:33:54 +08:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
ParseLine(output, section, line);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2022-05-27 18:18:37 +08:00
|
|
|
|
Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}");
|
2019-08-07 18:33:54 +08:00
|
|
|
|
}
|
2017-12-02 00:43:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-05-14 15:16:55 +08:00
|
|
|
|
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-03-30 16:18:09 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Invoked when a new <see cref="Section"/> has been entered.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="section">The entered <see cref="Section"/>.</param>
|
|
|
|
|
protected virtual void OnBeginNewSection(Section section)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 18:13:50 +08:00
|
|
|
|
protected virtual void ParseLine(T output, Section section, string line)
|
|
|
|
|
{
|
|
|
|
|
switch (section)
|
|
|
|
|
{
|
|
|
|
|
case Section.Colours:
|
2022-09-21 15:04:32 +08:00
|
|
|
|
HandleColours(output, line, false);
|
2018-03-13 18:13:50 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-16 22:35:55 +08:00
|
|
|
|
|
|
|
|
|
protected string StripComments(string line)
|
2018-07-16 07:04:41 +08:00
|
|
|
|
{
|
2021-10-27 12:04:41 +08:00
|
|
|
|
int index = line.AsSpan().IndexOf("//".AsSpan());
|
2018-07-16 07:04:41 +08:00
|
|
|
|
if (index > 0)
|
|
|
|
|
return line.Substring(0, index);
|
2019-02-28 12:31:40 +08:00
|
|
|
|
|
2018-07-16 07:04:41 +08:00
|
|
|
|
return line;
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2024-07-23 23:35:25 +08:00
|
|
|
|
private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, KeyValuePair<string, string> pair)
|
2018-03-13 18:13:50 +08:00
|
|
|
|
{
|
2018-10-05 10:19:01 +08:00
|
|
|
|
if (split.Length != 3 && split.Length != 4)
|
|
|
|
|
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}");
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-10-05 10:55:59 +08:00
|
|
|
|
Color4 colour;
|
2018-10-05 10:19:01 +08:00
|
|
|
|
|
2018-10-05 10:55:59 +08:00
|
|
|
|
try
|
|
|
|
|
{
|
2022-09-21 15:04:32 +08:00
|
|
|
|
byte alpha = allowAlpha && split.Length == 4 ? byte.Parse(split[3]) : (byte)255;
|
2020-06-25 13:15:26 +08:00
|
|
|
|
colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha);
|
2018-10-05 10:55:59 +08:00
|
|
|
|
}
|
2019-04-25 16:36:17 +08:00
|
|
|
|
catch
|
2018-10-05 10:19:01 +08:00
|
|
|
|
{
|
2018-03-13 18:13:50 +08:00
|
|
|
|
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
|
2018-10-05 10:19:01 +08:00
|
|
|
|
}
|
2024-07-23 23:35:25 +08:00
|
|
|
|
return colour;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
|
|
|
|
|
{
|
|
|
|
|
var pair = SplitKeyVal(line);
|
|
|
|
|
|
|
|
|
|
string[] split = pair.Value.Split(',');
|
|
|
|
|
Color4 colour = convertSettingStringToColor4(split, allowAlpha, pair);
|
|
|
|
|
|
|
|
|
|
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-03-13 18:13:50 +08:00
|
|
|
|
if (isCombo)
|
|
|
|
|
{
|
|
|
|
|
if (!(output is IHasComboColours tHasComboColours)) return;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-08-15 22:00:22 +08:00
|
|
|
|
tHasComboColours.CustomComboColours.Add(colour);
|
2018-03-13 18:13:50 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!(output is IHasCustomColours tHasCustomColours)) return;
|
2019-02-28 12:31:40 +08:00
|
|
|
|
|
2018-03-13 18:13:50 +08:00
|
|
|
|
tHasCustomColours.CustomColours[pair.Key] = colour;
|
|
|
|
|
}
|
2024-07-23 23:35:25 +08:00
|
|
|
|
bool isInputOverlayText = pair.Key.StartsWith(@"InputOverlayText");
|
|
|
|
|
|
|
|
|
|
if (isInputOverlayText)
|
|
|
|
|
{
|
|
|
|
|
if (!(output is SkinConfiguration tSkinConfiguration)) return;
|
|
|
|
|
tSkinConfiguration.InputOverlayText = colour;
|
|
|
|
|
}
|
2018-03-13 18:13:50 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-11-08 12:30:11 +08:00
|
|
|
|
protected KeyValuePair<string, string> SplitKeyVal(string line, char separator = ':', bool shouldTrim = true)
|
2017-12-02 00:43:33 +08:00
|
|
|
|
{
|
2022-12-27 16:41:58 +08:00
|
|
|
|
string[] split = line.Split(separator, 2, shouldTrim ? StringSplitOptions.TrimEntries : StringSplitOptions.None);
|
2022-11-08 12:30:11 +08:00
|
|
|
|
|
2018-01-04 19:04:52 +08:00
|
|
|
|
return new KeyValuePair<string, string>
|
|
|
|
|
(
|
2022-11-08 12:30:11 +08:00
|
|
|
|
split[0],
|
|
|
|
|
split.Length > 1 ? split[1] : string.Empty
|
2018-01-04 19:04:52 +08:00
|
|
|
|
);
|
2017-12-02 00:43:33 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-11-21 15:06:36 +08:00
|
|
|
|
protected string CleanFilename(string path) => path
|
|
|
|
|
// User error which is supported by stable (https://github.com/ppy/osu/issues/21204)
|
|
|
|
|
.Replace(@"\\", @"\")
|
|
|
|
|
.Trim('"')
|
|
|
|
|
.ToStandardisedPath();
|
2020-01-23 23:23:53 +08:00
|
|
|
|
|
2022-06-13 14:40:11 +08:00
|
|
|
|
public enum Section
|
2017-12-02 00:43:33 +08:00
|
|
|
|
{
|
|
|
|
|
General,
|
|
|
|
|
Editor,
|
|
|
|
|
Metadata,
|
|
|
|
|
Difficulty,
|
|
|
|
|
Events,
|
|
|
|
|
TimingPoints,
|
|
|
|
|
Colours,
|
|
|
|
|
HitObjects,
|
|
|
|
|
Variables,
|
2020-03-30 16:18:09 +08:00
|
|
|
|
Fonts,
|
2020-04-05 05:10:12 +08:00
|
|
|
|
CatchTheBeat,
|
|
|
|
|
Mania,
|
2017-12-02 00:43:33 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2022-06-20 13:56:04 +08:00
|
|
|
|
internal class LegacySampleControlPoint : SampleControlPoint, IEquatable<LegacySampleControlPoint>
|
2018-06-28 17:20:43 +08:00
|
|
|
|
{
|
|
|
|
|
public int CustomSampleBank;
|
|
|
|
|
|
2019-06-30 20:58:30 +08:00
|
|
|
|
public override HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
|
2018-06-28 17:20:43 +08:00
|
|
|
|
{
|
2023-05-16 15:29:24 +08:00
|
|
|
|
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
|
|
|
|
|
{
|
|
|
|
|
return legacy.With(
|
|
|
|
|
newCustomSampleBank: legacy.CustomSampleBank > 0 ? legacy.CustomSampleBank : CustomSampleBank,
|
|
|
|
|
newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume,
|
|
|
|
|
newBank: legacy.BankSpecified ? legacy.Bank : SampleBank
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-06-28 17:20:43 +08:00
|
|
|
|
|
2023-05-16 15:29:24 +08:00
|
|
|
|
return base.ApplyTo(hitSampleInfo);
|
2018-06-28 17:20:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-20 15:52:01 +08:00
|
|
|
|
public override bool IsRedundant(ControlPoint? existing)
|
2020-04-17 16:06:12 +08:00
|
|
|
|
=> base.IsRedundant(existing)
|
2020-04-17 16:04:09 +08:00
|
|
|
|
&& existing is LegacySampleControlPoint existingSample
|
|
|
|
|
&& CustomSampleBank == existingSample.CustomSampleBank;
|
2021-01-05 12:41:31 +08:00
|
|
|
|
|
|
|
|
|
public override void CopyFrom(ControlPoint other)
|
|
|
|
|
{
|
|
|
|
|
base.CopyFrom(other);
|
|
|
|
|
|
|
|
|
|
CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
|
|
|
|
|
}
|
2022-06-20 13:56:04 +08:00
|
|
|
|
|
|
|
|
|
public override bool Equals(ControlPoint? other)
|
|
|
|
|
=> other is LegacySampleControlPoint otherLegacySampleControlPoint
|
|
|
|
|
&& Equals(otherLegacySampleControlPoint);
|
|
|
|
|
|
|
|
|
|
public bool Equals(LegacySampleControlPoint? other)
|
|
|
|
|
=> base.Equals(other)
|
|
|
|
|
&& CustomSampleBank == other.CustomSampleBank;
|
|
|
|
|
|
|
|
|
|
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
|
|
|
|
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank);
|
2018-06-28 17:20:43 +08:00
|
|
|
|
}
|
2017-12-02 00:43:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|