From 77e422409cdc9e6b216724f778c2b32617878ebf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 17:00:24 +0900 Subject: [PATCH 1/5] Add `SkinInfo.InstantiationInfo` to allow creating different skin types --- .../TestSceneHitCircleArea.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- ...60743_AddSkinInstantiationInfo.Designer.cs | 508 ++++++++++++++++++ ...20210511060743_AddSkinInstantiationInfo.cs | 22 + .../Migrations/OsuDbContextModelSnapshot.cs | 6 +- osu.Game/Skinning/DefaultLegacySkin.cs | 10 +- osu.Game/Skinning/DefaultSkin.cs | 10 +- osu.Game/Skinning/SkinInfo.cs | 36 +- osu.Game/Skinning/SkinManager.cs | 31 +- 9 files changed, 597 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 0649989dc0..1fdcd73dde 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = new SkinProvidingContainer(new DefaultSkin()) + Child = new SkinProvidingContainer(new DefaultSkin(null)) { RelativeSizeAxes = Axes.Both, Child = drawableHitCircle = new DrawableHitCircle(hitCircle) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e0eeaf6db0..3576b149bf 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps public bool SkinLoaded => skin.IsResultAvailable; public ISkin Skin => skin.Value; - protected virtual ISkin GetSkin() => new DefaultSkin(); + protected virtual ISkin GetSkin() => new DefaultSkin(null); private readonly RecyclableLazy skin; public abstract Stream GetStream(string storagePath); diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs new file mode 100644 index 0000000000..b808c648da --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210511060743_AddSkinInstantiationInfo")] + partial class AddSkinInstantiationInfo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs new file mode 100644 index 0000000000..1d5b0769a4 --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInstantiationInfo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "InstantiationInfo", + table: "SkinInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "InstantiationInfo", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index ec4461ca56..d4bde50b60 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -141,8 +141,6 @@ namespace osu.Game.Migrations b.Property("TitleUnicode"); - b.Property("VideoFile"); - b.HasKey("ID"); b.ToTable("BeatmapMetadata"); @@ -352,7 +350,7 @@ namespace osu.Game.Migrations b.Property("TotalScore"); - b.Property("UserID") + b.Property("UserID") .HasColumnName("UserID"); b.Property("UserString") @@ -402,6 +400,8 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("InstantiationInfo"); + b.Property("Name"); b.HasKey("ID"); diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 4027cc650d..564be8630e 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -10,7 +10,12 @@ namespace osu.Game.Skinning public class DefaultLegacySkin : LegacySkin { public DefaultLegacySkin(IResourceStore storage, IStorageResourceProvider resources) - : base(Info, storage, resources, string.Empty) + : this(Info, storage, resources) + { + } + + public DefaultLegacySkin(SkinInfo skin, IResourceStore storage, IStorageResourceProvider resources) + : base(skin, storage, resources, string.Empty) { Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); Configuration.AddComboColours( @@ -27,7 +32,8 @@ namespace osu.Game.Skinning { ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. Name = "osu!classic", - Creator = "team osu!" + Creator = "team osu!", + InstantiationInfo = typeof(DefaultLegacySkin).AssemblyQualifiedName, }; } } diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 0b3f5f3cde..fcd874d6ca 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -8,14 +8,20 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.IO; using osuTK.Graphics; namespace osu.Game.Skinning { public class DefaultSkin : Skin { - public DefaultSkin() - : base(SkinInfo.Default) + public DefaultSkin(IStorageResourceProvider resources) + : this(SkinInfo.Default, resources) + { + } + + public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources) + : base(skin) { Configuration = new DefaultSkinConfiguration(); } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index aaccbefb3d..2e29808cb4 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.IO; namespace osu.Game.Skinning { @@ -22,7 +25,35 @@ namespace osu.Game.Skinning public string Creator { get; set; } - public List Files { get; set; } + private string instantiationInfo; + + public string InstantiationInfo + { + get => instantiationInfo; + set => instantiationInfo = abbreviateInstantiationInfo(value); + } + + private string abbreviateInstantiationInfo(string value) + { + // exclude version onwards, matching only on namespace and type. + // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. + return string.Join(',', value.Split(',').Take(2)); + } + + public virtual Skin CreateInstance(IResourceStore legacyDefaultResources, IStorageResourceProvider resources) + { + var type = string.IsNullOrEmpty(InstantiationInfo) + // handle the case of skins imported before InstantiationInfo was added. + ? typeof(LegacySkin) + : Type.GetType(InstantiationInfo); + + if (type == typeof(DefaultLegacySkin)) + return (Skin)Activator.CreateInstance(type, this, legacyDefaultResources, resources); + + return (Skin)Activator.CreateInstance(type, this, resources); + } + + public List Files { get; set; } = new List(); public List Settings { get; set; } @@ -32,7 +63,8 @@ namespace osu.Game.Skinning { ID = DEFAULT_SKIN, Name = "osu!lazer", - Creator = "team osu!" + Creator = "team osu!", + InstantiationInfo = typeof(DefaultSkin).AssemblyQualifiedName, }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ac4d63159a..dbb9dcb7fc 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -36,7 +36,7 @@ namespace osu.Game.Skinning private readonly IResourceStore legacyDefaultResources; - public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin()); + public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin(null)); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; public override IEnumerable HandledExtensions => new[] { ".osk" }; @@ -105,7 +105,7 @@ namespace osu.Game.Skinning { // we need to populate early to create a hash based off skin.ini contents if (item.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) - populateMetadata(item); + populateMetadata(item, GetSkin(item)); if (item.Creator != null && item.Creator != unknown_creator_string) { @@ -122,18 +122,20 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); + var instance = GetSkin(model); + + model.InstantiationInfo ??= instance.GetType().AssemblyQualifiedName; + if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) - populateMetadata(model); + populateMetadata(model, instance); } - private void populateMetadata(SkinInfo item) + private void populateMetadata(SkinInfo item, Skin instance) { - Skin reference = GetSkin(item); - - if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) + if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name)) { - item.Name = reference.Configuration.SkinInfo.Name; - item.Creator = reference.Configuration.SkinInfo.Creator; + item.Name = instance.Configuration.SkinInfo.Name; + item.Creator = instance.Configuration.SkinInfo.Creator; } else { @@ -147,16 +149,7 @@ namespace osu.Game.Skinning /// /// The skin to lookup. /// A instance correlating to the provided . - public Skin GetSkin(SkinInfo skinInfo) - { - if (skinInfo == SkinInfo.Default) - return new DefaultSkin(); - - if (skinInfo == DefaultLegacySkin.Info) - return new DefaultLegacySkin(legacyDefaultResources, this); - - return new LegacySkin(skinInfo, this); - } + public Skin GetSkin(SkinInfo skinInfo) => skinInfo.CreateInstance(legacyDefaultResources, this); /// /// Perform a lookup query on available s. From 4464204e336688bcfce0266c2014ec20a7978e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 22:18:15 +0200 Subject: [PATCH 2/5] Mark all skin ctors used via reflection in `SkinInfo.CreateInstance()` --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 ++ osu.Game/Skinning/DefaultSkin.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 1 + 3 files changed, 5 insertions(+) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 564be8630e..7adf53e5e7 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.IO.Stores; using osu.Game.IO; using osuTK.Graphics; @@ -9,6 +10,7 @@ namespace osu.Game.Skinning { public class DefaultLegacySkin : LegacySkin { + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultLegacySkin(IResourceStore storage, IStorageResourceProvider resources) : this(Info, storage, resources) { diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index fcd874d6ca..745bdf0bb2 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,6 +21,7 @@ namespace osu.Game.Skinning { } + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources) : base(skin) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index eae3b69233..74250f9684 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -52,6 +52,7 @@ namespace osu.Game.Skinning private readonly Dictionary maniaConfigurations = new Dictionary(); + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public LegacySkin(SkinInfo skin, IStorageResourceProvider resources) : this(skin, new LegacySkinResourceStore(skin, resources.Files), resources, "skin.ini") { From 1b579dd83801849c33ce06a685eab48230cdd648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 22:42:26 +0200 Subject: [PATCH 3/5] Extract invariant instantiation info extension method --- osu.Game/Extensions/TypeExtensions.cs | 31 +++++++++++++++++++++++++++ osu.Game/Rulesets/Ruleset.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 16 +------------- osu.Game/Skinning/SkinInfo.cs | 16 +------------- osu.Game/Skinning/SkinManager.cs | 3 ++- 5 files changed, 37 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Extensions/TypeExtensions.cs diff --git a/osu.Game/Extensions/TypeExtensions.cs b/osu.Game/Extensions/TypeExtensions.cs new file mode 100644 index 0000000000..2e93c81758 --- /dev/null +++ b/osu.Game/Extensions/TypeExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; + +namespace osu.Game.Extensions +{ + internal static class TypeExtensions + { + /// + /// Returns 's + /// with the assembly version, culture and public key token values removed. + /// + /// + /// This method is usually used in extensibility scenarios (i.e. for custom rulesets or skins) + /// when a version-agnostic identifier associated with a C# class - potentially originating from + /// an external assembly - is needed. + /// Leaving only the type and assembly names in such a scenario allows to preserve compatibility + /// across assembly versions. + /// + internal static string GetInvariantInstantiationInfo(this Type type) + { + string assemblyQualifiedName = type.AssemblyQualifiedName; + if (assemblyQualifiedName == null) + throw new ArgumentException($"{type}'s assembly-qualified name is null. Ensure that it is a concrete type and not a generic type parameter.", nameof(type)); + + return string.Join(',', assemblyQualifiedName.Split(',').Take(2)); + } + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 7f0c27adfc..7bdf84ace4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -26,6 +26,7 @@ using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; +using osu.Game.Extensions; using osu.Game.Rulesets.Filter; using osu.Game.Screens.Ranking.Statistics; @@ -135,7 +136,7 @@ namespace osu.Game.Rulesets Name = Description, ShortName = ShortName, ID = (this as ILegacyRuleset)?.LegacyID, - InstantiationInfo = GetType().AssemblyQualifiedName, + InstantiationInfo = GetType().GetInvariantInstantiationInfo(), Available = true, }; } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index d5aca8c650..702bf35fa8 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Newtonsoft.Json; using osu.Framework.Testing; @@ -18,20 +17,7 @@ namespace osu.Game.Rulesets public string ShortName { get; set; } - private string instantiationInfo; - - public string InstantiationInfo - { - get => instantiationInfo; - set => instantiationInfo = abbreviateInstantiationInfo(value); - } - - private string abbreviateInstantiationInfo(string value) - { - // exclude version onwards, matching only on namespace and type. - // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. - return string.Join(',', value.Split(',').Take(2)); - } + public string InstantiationInfo { get; set; } [JsonIgnore] public bool Available { get; set; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 2e29808cb4..a61a6dd1ce 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; @@ -25,20 +24,7 @@ namespace osu.Game.Skinning public string Creator { get; set; } - private string instantiationInfo; - - public string InstantiationInfo - { - get => instantiationInfo; - set => instantiationInfo = abbreviateInstantiationInfo(value); - } - - private string abbreviateInstantiationInfo(string value) - { - // exclude version onwards, matching only on namespace and type. - // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. - return string.Join(',', value.Split(',').Take(2)); - } + public string InstantiationInfo { get; set; } public virtual Skin CreateInstance(IResourceStore legacyDefaultResources, IStorageResourceProvider resources) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index dbb9dcb7fc..b4051286aa 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -22,6 +22,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; @@ -124,7 +125,7 @@ namespace osu.Game.Skinning var instance = GetSkin(model); - model.InstantiationInfo ??= instance.GetType().AssemblyQualifiedName; + model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model, instance); From a6aec6e0074db91968c3526c4f9c556fba1c2329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 23:34:25 +0200 Subject: [PATCH 4/5] Fix missed `InstantiationInfo` setter usages --- osu.Game/Skinning/DefaultLegacySkin.cs | 3 ++- osu.Game/Skinning/SkinInfo.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 7adf53e5e7..b05c309e4e 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using osu.Framework.IO.Stores; +using osu.Game.Extensions; using osu.Game.IO; using osuTK.Graphics; @@ -35,7 +36,7 @@ namespace osu.Game.Skinning ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. Name = "osu!classic", Creator = "team osu!", - InstantiationInfo = typeof(DefaultLegacySkin).AssemblyQualifiedName, + InstantiationInfo = typeof(DefaultLegacySkin).GetInvariantInstantiationInfo() }; } } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index a61a6dd1ce..bc57a8e71c 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; namespace osu.Game.Skinning @@ -50,7 +51,7 @@ namespace osu.Game.Skinning ID = DEFAULT_SKIN, Name = "osu!lazer", Creator = "team osu!", - InstantiationInfo = typeof(DefaultSkin).AssemblyQualifiedName, + InstantiationInfo = typeof(DefaultSkin).GetInvariantInstantiationInfo() }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; From 27ca7d0f4fa24f8837607dab5f3009459e92b940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 23:53:39 +0200 Subject: [PATCH 5/5] Actually annotate the correct ctor --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index b05c309e4e..f30130b1fb 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -11,12 +11,12 @@ namespace osu.Game.Skinning { public class DefaultLegacySkin : LegacySkin { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultLegacySkin(IResourceStore storage, IStorageResourceProvider resources) : this(Info, storage, resources) { } + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultLegacySkin(SkinInfo skin, IResourceStore storage, IStorageResourceProvider resources) : base(skin, storage, resources, string.Empty) {