From 5b0c81e1040c59246805af0a143344881ad2e648 Mon Sep 17 00:00:00 2001 From: KIT! Date: Fri, 8 Nov 2024 15:54:59 +0100 Subject: [PATCH] Fixed migration issues from CHN * WordListEntries are now loaded using FirstOrDefault to avoid crashes when using malformed custom wordlists * Added missing field UnlockedUraSongIdList to AddMyDonEntryCommand --- GameDatabase/Entities/UserDatum.cs | 6 - ...41108132749_RemoveUnusedArrays.Designer.cs | 576 ++++++++++++++++++ .../20241108132749_RemoveUnusedArrays.cs | 67 ++ .../Migrations/TaikoDbContextModelSnapshot.cs | 2 +- .../Handlers/AddMyDonEntryCommand.cs | 3 +- TaikoLocalServer/Services/GameDataService.cs | 17 +- 6 files changed, 655 insertions(+), 16 deletions(-) create mode 100644 GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.Designer.cs create mode 100644 GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.cs diff --git a/GameDatabase/Entities/UserDatum.cs b/GameDatabase/Entities/UserDatum.cs index 4bcd5ca..2ead41f 100644 --- a/GameDatabase/Entities/UserDatum.cs +++ b/GameDatabase/Entities/UserDatum.cs @@ -12,7 +12,6 @@ namespace GameDatabase.Entities public List FavoriteSongsArray { get; set; } = []; public List ToneFlgArray { get; set; } = [0]; public List TitleFlgArray { get; set; } = []; - public string CostumeFlgArray { get; set; } = "[[],[],[],[],[]]"; public List UnlockedKigurumi { get; set; } = [0]; public List UnlockedHead { get; set; } = [0]; public List UnlockedBody { get; set; }= [0]; @@ -23,12 +22,9 @@ namespace GameDatabase.Entities public int NotesPosition { get; set; } public bool IsVoiceOn { get; set; } = true; public bool IsSkipOn { get; set; } - public string DifficultyPlayedArray { get; set; } = "[]"; public uint DifficultyPlayedCourse { get; set; } public uint DifficultyPlayedStar { get; set; } public uint DifficultyPlayedSort { get; set; } - - public string DifficultySettingArray { get; set; } = "[]"; public uint DifficultySettingCourse { get; set; } public uint DifficultySettingStar { get; set; } public uint DifficultySettingSort { get; set; } @@ -38,8 +34,6 @@ namespace GameDatabase.Entities public uint ColorBody { get; set; } public uint ColorFace { get; set; } public uint ColorLimb { get; set; } - - public string CostumeData { get; set; } = "[]"; public uint CurrentKigurumi { get; set; } public uint CurrentHead { get; set; } public uint CurrentBody { get; set; } diff --git a/GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.Designer.cs b/GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.Designer.cs new file mode 100644 index 0000000..f0ca133 --- /dev/null +++ b/GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.Designer.cs @@ -0,0 +1,576 @@ +// +using System; +using GameDatabase.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GameDatabase.Migrations +{ + [DbContext(typeof(TaikoDbContext))] + [Migration("20241108132749_RemoveUnusedArrays")] + partial class RemoveUnusedArrays + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); + + modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("IsWin") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "SongId", "Difficulty"); + + b.ToTable("AiScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("SectionIndex") + .HasColumnType("INTEGER"); + + b.Property("Crown") + .HasColumnType("INTEGER"); + + b.Property("DrumrollCount") + .HasColumnType("INTEGER"); + + b.Property("GoodCount") + .HasColumnType("INTEGER"); + + b.Property("IsWin") + .HasColumnType("INTEGER"); + + b.Property("MissCount") + .HasColumnType("INTEGER"); + + b.Property("OkCount") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "SongId", "Difficulty", "SectionIndex"); + + b.ToTable("AiSectionScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Card", b => + { + b.Property("AccessCode") + .HasColumnType("TEXT"); + + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.HasKey("AccessCode"); + + b.HasIndex("Baid"); + + b.ToTable("Card", (string)null); + }); + + modelBuilder.Entity("GameDatabase.Entities.Credential", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Baid"); + + b.ToTable("Credential", (string)null); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("DanId") + .HasColumnType("INTEGER"); + + b.Property("DanType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("ArrivalSongCount") + .HasColumnType("INTEGER"); + + b.Property("ClearState") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0u); + + b.Property("ComboCountTotal") + .HasColumnType("INTEGER"); + + b.Property("SoulGaugeTotal") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "DanId", "DanType"); + + b.ToTable("DanScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("DanId") + .HasColumnType("INTEGER"); + + b.Property("DanType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("SongNumber") + .HasColumnType("INTEGER"); + + b.Property("BadCount") + .HasColumnType("INTEGER"); + + b.Property("ComboCount") + .HasColumnType("INTEGER"); + + b.Property("DrumrollCount") + .HasColumnType("INTEGER"); + + b.Property("GoodCount") + .HasColumnType("INTEGER"); + + b.Property("HighScore") + .HasColumnType("INTEGER"); + + b.Property("OkCount") + .HasColumnType("INTEGER"); + + b.Property("PlayScore") + .HasColumnType("INTEGER"); + + b.Property("TotalHitCount") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "DanId", "DanType", "SongNumber"); + + b.ToTable("DanStageScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("BestCrown") + .HasColumnType("INTEGER"); + + b.Property("BestRate") + .HasColumnType("INTEGER"); + + b.Property("BestScore") + .HasColumnType("INTEGER"); + + b.Property("BestScoreRank") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "SongId", "Difficulty"); + + b.ToTable("SongBestData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("ComboCount") + .HasColumnType("INTEGER"); + + b.Property("Crown") + .HasColumnType("INTEGER"); + + b.Property("Difficulty") + .HasColumnType("INTEGER"); + + b.Property("DrumrollCount") + .HasColumnType("INTEGER"); + + b.Property("GoodCount") + .HasColumnType("INTEGER"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("MissCount") + .HasColumnType("INTEGER"); + + b.Property("OkCount") + .HasColumnType("INTEGER"); + + b.Property("PlayTime") + .HasColumnType("datetime"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("ScoreRank") + .HasColumnType("INTEGER"); + + b.Property("ScoreRate") + .HasColumnType("INTEGER"); + + b.Property("Skipped") + .HasColumnType("INTEGER"); + + b.Property("SongId") + .HasColumnType("INTEGER"); + + b.Property("SongNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Baid"); + + b.ToTable("SongPlayData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Token", b => + { + b.Property("Baid") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.HasKey("Baid", "Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("GameDatabase.Entities.UserDatum", b => + { + b.Property("Baid") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AchievementDisplayDifficulty") + .HasColumnType("INTEGER"); + + b.Property("AiWinCount") + .HasColumnType("INTEGER"); + + b.Property("ColorBody") + .HasColumnType("INTEGER"); + + b.Property("ColorFace") + .HasColumnType("INTEGER"); + + b.Property("ColorLimb") + .HasColumnType("INTEGER"); + + b.Property("CostumeData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CostumeFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CurrentBody") + .HasColumnType("INTEGER"); + + b.Property("CurrentFace") + .HasColumnType("INTEGER"); + + b.Property("CurrentHead") + .HasColumnType("INTEGER"); + + b.Property("CurrentKigurumi") + .HasColumnType("INTEGER"); + + b.Property("CurrentPuchi") + .HasColumnType("INTEGER"); + + b.Property("DifficultyPlayedArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DifficultyPlayedCourse") + .HasColumnType("INTEGER"); + + b.Property("DifficultyPlayedSort") + .HasColumnType("INTEGER"); + + b.Property("DifficultyPlayedStar") + .HasColumnType("INTEGER"); + + b.Property("DifficultySettingArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DifficultySettingCourse") + .HasColumnType("INTEGER"); + + b.Property("DifficultySettingSort") + .HasColumnType("INTEGER"); + + b.Property("DifficultySettingStar") + .HasColumnType("INTEGER"); + + b.Property("DisplayAchievement") + .HasColumnType("INTEGER"); + + b.Property("DisplayDan") + .HasColumnType("INTEGER"); + + b.Property("FavoriteSongsArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GenericInfoFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("IsSkipOn") + .HasColumnType("INTEGER"); + + b.Property("IsVoiceOn") + .HasColumnType("INTEGER"); + + b.Property("LastPlayDatetime") + .HasColumnType("datetime"); + + b.Property("LastPlayMode") + .HasColumnType("INTEGER"); + + b.Property("MyDonName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MyDonNameLanguage") + .HasColumnType("INTEGER"); + + b.Property("NotesPosition") + .HasColumnType("INTEGER"); + + b.Property("OptionSetting") + .HasColumnType("INTEGER"); + + b.Property("SelectedToneId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitleFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitlePlateId") + .HasColumnType("INTEGER"); + + b.Property("ToneFlgArray") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedBody") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedFace") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedHead") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedKigurumi") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedPuchi") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedSongIdList") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnlockedUraSongIdList") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Baid"); + + b.ToTable("UserData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.AiScoreDatum", "Parent") + .WithMany("AiSectionScoreData") + .HasForeignKey("Baid", "SongId", "Difficulty") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Card", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Credential", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b => + { + b.HasOne("GameDatabase.Entities.DanScoreDatum", "Parent") + .WithMany("DanStageScoreData") + .HasForeignKey("Baid", "DanId", "DanType") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Ba") + .WithMany() + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ba"); + }); + + modelBuilder.Entity("GameDatabase.Entities.Token", b => + { + b.HasOne("GameDatabase.Entities.UserDatum", "Datum") + .WithMany("Tokens") + .HasForeignKey("Baid") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Datum"); + }); + + modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => + { + b.Navigation("AiSectionScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b => + { + b.Navigation("DanStageScoreData"); + }); + + modelBuilder.Entity("GameDatabase.Entities.UserDatum", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.cs b/GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.cs new file mode 100644 index 0000000..38eba1d --- /dev/null +++ b/GameDatabase/Migrations/20241108132749_RemoveUnusedArrays.cs @@ -0,0 +1,67 @@ +using GameDatabase.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GameDatabase.Migrations +{ + /// + public partial class RemoveUnusedArrays : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + //We messed up a few migrations before so this is aiming to fix the usual issues you can encounter when migrating from 08.18 and CHN. + + //Removing the following tables as they have been split a while ago and are not longer being used. + migrationBuilder.Sql(@"ALTER TABLE UserData DROP COLUMN CostumeData"); + migrationBuilder.Sql(@"ALTER TABLE UserData DROP COLUMN CostumeFlgArray"); + migrationBuilder.Sql(@"ALTER TABLE UserData DROP COLUMN DifficultyPlayedArray"); + migrationBuilder.Sql(@"ALTER TABLE UserData DROP COLUMN DifficultySettingArray"); + + //The default value of the previous migration was incorrectly set, causing a crash on load. + migrationBuilder.Sql(@"UPDATE UserData SET UnlockedUraSongIdList = '[]' WHERE UnlockedUraSongIdList = ''"); + migrationBuilder.Sql(@"UPDATE UserData SET UnlockedSongIdList = '[]' WHERE UnlockedSongIdList = ''"); + + //These arrays should not be empty + migrationBuilder.Sql(@"UPDATE UserData SET UnlockedPuchi = '[0]' WHERE UnlockedPuchi = '[]'"); + migrationBuilder.Sql(@"UPDATE UserData SET UnlockedHead = '[0]' WHERE UnlockedHead = '[]'"); + migrationBuilder.Sql(@"UPDATE UserData SET UnlockedFace = '[0]' WHERE UnlockedFace = '[]'"); + migrationBuilder.Sql(@"UPDATE UserData SET UnlockedBody = '[0]' WHERE UnlockedBody = '[]'"); + migrationBuilder.Sql(@"UPDATE UserData SET ToneFlgArray = '[0]' WHERE ToneFlgArray = '[]'"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CostumeData", + table: "UserData", + type: "TEXT", + nullable: false, + defaultValue: "[]"); + + migrationBuilder.AddColumn( + name: "CostumeFlgArray", + table: "UserData", + type: "TEXT", + nullable: false, + defaultValue: "[]"); + + migrationBuilder.AddColumn( + name: "DifficultyPlayedArray", + table: "UserData", + type: "TEXT", + nullable: false, + defaultValue: "[0,0,0]"); + + migrationBuilder.AddColumn( + name: "DifficultySettingArray", + table: "UserData", + type: "TEXT", + nullable: false, + defaultValue: "[0,0,0]"); + } + } +} diff --git a/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs b/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs index 4547fe1..d5b5029 100644 --- a/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs +++ b/GameDatabase/Migrations/TaikoDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace TaikoLocalServer.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.3"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b => { diff --git a/TaikoLocalServer/Handlers/AddMyDonEntryCommand.cs b/TaikoLocalServer/Handlers/AddMyDonEntryCommand.cs index 6e161c5..553bdff 100644 --- a/TaikoLocalServer/Handlers/AddMyDonEntryCommand.cs +++ b/TaikoLocalServer/Handlers/AddMyDonEntryCommand.cs @@ -33,7 +33,8 @@ public class AddMyDonEntryCommandHandler(TaikoDbContext context, ILogger dataSettings) : IGameDataSer var musicId = musicInfo.Id; var musicNameKey = $"song_{musicId}"; var musicArtistKey = $"song_sub_{musicId}"; - var musicName = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).JapaneseText; - var musicArtist = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).JapaneseText; - var musicNameEn = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).EnglishUsText; - var musicArtistEn = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).EnglishUsText; - var musicNameCn = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).ChineseTText; - var musicArtistCn = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).ChineseTText; - var musicNameKo = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).KoreanText; - var musicArtistKo = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).KoreanText; + //Using FirstOrDefault because the server could crash when the wordlist had missing song_sub entries. + var musicName = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicNameKey)?.JapaneseText ?? ""; + var musicArtist = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicArtistKey)?.JapaneseText ?? ""; + var musicNameEn = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicNameKey)?.EnglishUsText ?? ""; + var musicArtistEn = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicArtistKey)?.EnglishUsText ?? ""; + var musicNameCn = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicNameKey)?.ChineseTText ?? ""; + var musicArtistCn = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicArtistKey)?.ChineseTText ?? ""; + var musicNameKo = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicNameKey)?.KoreanText ?? ""; + var musicArtistKo = wordlistData.WordListEntries.FirstOrDefault(entry => entry.Key == musicArtistKey)?.KoreanText ?? ""; var musicGenre = musicInfo.Genre; var musicStarEasy = musicInfo.StarEasy; var musicStarNormal = musicInfo.StarNormal;