-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLegacyDecoder.cs
224 lines (183 loc) · 7.84 KB
/
LegacyDecoder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// 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.Collections.Generic;
using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO;
using osu.Game.Rulesets.Objects.Legacy;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.Formats
{
public abstract class LegacyDecoder<T> : Decoder<T>
where T : new()
{
public const int LATEST_VERSION = 14;
public const int MAX_COMBO_COLOUR_COUNT = 8;
/// <summary>
/// The .osu format (beatmap) version.
///
/// osu!stable's versions end at <see cref="LATEST_VERSION"/>.
/// osu!lazer's versions starts at <see cref="LegacyBeatmapEncoder.FIRST_LAZER_VERSION"/>.
/// </summary>
protected readonly int FormatVersion;
protected LegacyDecoder(int version)
{
FormatVersion = version;
}
protected override void ParseStreamInto(LineBufferedReader stream, T output)
{
Section section = Section.General;
string? line;
while ((line = stream.ReadLine()) != null)
{
if (ShouldSkipLine(line))
continue;
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();
if (line.StartsWith('[') && line.EndsWith(']'))
{
if (!Enum.TryParse(line[1..^1], out section))
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
OnBeginNewSection(section);
continue;
}
try
{
ParseLine(output, section, line);
}
catch (Exception e)
{
Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}");
}
}
}
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal);
/// <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)
{
}
protected virtual void ParseLine(T output, Section section, string line)
{
switch (section)
{
case Section.Colours:
HandleColours(output, line, false);
return;
}
}
protected string StripComments(string line)
{
int index = line.AsSpan().IndexOf("//".AsSpan());
if (index > 0)
return line.Substring(0, index);
return line;
}
private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, KeyValuePair<string, string> pair)
{
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}");
Color4 colour;
try
{
byte alpha = allowAlpha && split.Length == 4 ? byte.Parse(split[3]) : (byte)255;
colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha);
}
catch
{
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
}
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)
&& int.TryParse(pair.Key[5..], out int comboIndex)
&& comboIndex >= 1 && comboIndex <= MAX_COMBO_COLOUR_COUNT;
if (isCombo)
{
if (!(output is IHasComboColours tHasComboColours)) return;
tHasComboColours.CustomComboColours.Add(colour);
}
else
{
if (!(output is IHasCustomColours tHasCustomColours)) return;
tHasCustomColours.CustomColours[pair.Key] = colour;
}
}
protected KeyValuePair<string, string> SplitKeyVal(string line, char separator = ':', bool shouldTrim = true)
{
string[] split = line.Split(separator, 2, shouldTrim ? StringSplitOptions.TrimEntries : StringSplitOptions.None);
return new KeyValuePair<string, string>
(
split[0],
split.Length > 1 ? split[1] : string.Empty
);
}
protected string CleanFilename(string path) => path
// User error which is supported by stable (https://github.com/ppy/osu/issues/21204)
.Replace(@"\\", @"\")
.Trim('"')
.ToStandardisedPath();
public enum Section
{
General,
Editor,
Metadata,
Difficulty,
Events,
TimingPoints,
Colours,
HitObjects,
Variables,
Fonts,
CatchTheBeat,
Mania,
}
internal class LegacySampleControlPoint : SampleControlPoint, IEquatable<LegacySampleControlPoint>
{
public int CustomSampleBank;
public override HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
{
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
);
}
return base.ApplyTo(hitSampleInfo);
}
public override bool IsRedundant(ControlPoint? existing)
=> base.IsRedundant(existing)
&& existing is LegacySampleControlPoint existingSample
&& CustomSampleBank == existingSample.CustomSampleBank;
public override void CopyFrom(ControlPoint other)
{
base.CopyFrom(other);
CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
}
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);
}
}
}