From 2d6eb38dffe174163b0f18871fc3e9a7563fb6ff Mon Sep 17 00:00:00 2001 From: SirusDoma Date: Sun, 19 Apr 2020 15:02:55 +0700 Subject: [PATCH] Fix Music ID Handling Also, Music ID field is now disabled --- Sources/AssetManager.cs | 66 +++- Sources/Forms/ConverterForm.cs | 548 +++++++++++++++-------------- Sources/Forms/MainForm.Designer.cs | 2 +- Sources/Forms/MainForm.cs | 63 +++- Sources/Vox/MusicDb.cs | 25 +- 5 files changed, 418 insertions(+), 286 deletions(-) diff --git a/Sources/AssetManager.cs b/Sources/AssetManager.cs index e9898ea..fc95b1a 100644 --- a/Sources/AssetManager.cs +++ b/Sources/AssetManager.cs @@ -12,6 +12,8 @@ namespace VoxCharger { #region --- Properties --- private static List MixList = new List(); + private static MusicDb InternalHeaders = null; + private static int LastOriginalID = 0; public static string MixName { get; private set; } @@ -32,13 +34,6 @@ namespace VoxCharger if (!File.Exists(dbFilename)) throw new FormatException("Invalid Game Directory"); - // Validate whether cache exists, perform full load when cache is not available - string cacheFilename = Path.Combine(gamePath, @"data_mods\_cache\others\music_db.xml"); - - // Load original headers data - Headers = new MusicDb(); - Headers.Load(File.Exists(cacheFilename) ? cacheFilename : dbFilename); - // Look for other mixes MixList.Clear(); string modsPath = Path.Combine(gamePath, @"data_mods\"); @@ -59,10 +54,10 @@ namespace VoxCharger continue; // Confirmed mod path, append into music db, ignore cache to avoid uncached mix being excluded - Headers.Load(dbFilename, true); MixList.Add(modName); } + LastOriginalID = 0; GamePath = gamePath; } @@ -77,6 +72,9 @@ namespace VoxCharger Directory.CreateDirectory(Path.Combine(mixPath, @"music\")); Directory.CreateDirectory(Path.Combine(mixPath, @"others\")); + // Load Existing song DB to avoid duplicate id + LoadInternalDb(mixName); + // Create empty db MdbFilename = Path.Combine(mixPath, @"others\music_db.merged.xml"); File.WriteAllText(MdbFilename, ""); @@ -93,7 +91,8 @@ namespace VoxCharger if (!Directory.Exists(mixPath)) throw new DirectoryNotFoundException("Mix directory missing"); - // No way it happen since combo box is dropdownlist, but well.. :v + // Load Existing song DB to avoid duplicate id + LoadInternalDb(mixName); if (!string.IsNullOrEmpty(mixName) && !MixList.Contains(mixName)) MixList.Add(mixName); @@ -110,6 +109,38 @@ namespace VoxCharger return MixList.ToArray(); } + private static void LoadInternalDb(string mixName) + { + // First, we need to populate available ids outside selected mix + // This will prevent duplicate ID not only with originals but with other mixes too + + // Load original music, ignore cache to avoid uncached mix being excluded or included + string dbFilename = Path.Combine(GamePath, @"data\others\music_db.xml"); + string modsPath = Path.Combine(GamePath, @"data_mods\"); + + // Load original headers data + InternalHeaders = new MusicDb(); + InternalHeaders.Load(dbFilename); + LastOriginalID = InternalHeaders.LastID; + + // Load other music db + foreach (var modDir in Directory.GetDirectories(modsPath)) + { + // Get directory name, exclude selected mix + string modName = new DirectoryInfo(modDir).Name; + if (modName == "_cache" || modName == mixName) + continue; + + // Validate whether the mod is a mix mod + // Do not skip unsupported mods, just read the db and ignore the rest of assets + dbFilename = Path.Combine(modDir, @"others\music_db.merged.xml"); + if (!File.Exists(dbFilename)) + continue; + + // Confirmed mod path, append into music db + InternalHeaders.Load(dbFilename, true); + } + } #endregion #region --- Asset Management --- @@ -217,6 +248,23 @@ namespace VoxCharger #endregion #region --- Asset Identifier --- + public static int GetNextMusicID() + { + // TODO: This probably inefficient in scenario where one or more mix has gap between each ids + // This could be waste to those gaps, and eat up our precious limited id + // However, these gaps may indicate deleted song, which should be taken by omnimix + + int id = InternalHeaders.LastID + 1; + while (InternalHeaders.Contains(id) || Headers.Contains(id)) // Contains is O(1) so its should be fine + id++; + + return id; + } + + public static bool ValidateMusicID(int id) + { + return !InternalHeaders.Contains(id); + } public static string GetDifficultyCodes(VoxHeader header, Difficulty difficulty) { diff --git a/Sources/Forms/ConverterForm.cs b/Sources/Forms/ConverterForm.cs index 6f4683e..21ec1e4 100644 --- a/Sources/Forms/ConverterForm.cs +++ b/Sources/Forms/ConverterForm.cs @@ -37,8 +37,9 @@ namespace VoxCharger private bool converter; private Dictionary charts = new Dictionary(); - public VoxHeader Header { get; private set; } = null; - public Action Action { get; private set; } = null; + public VoxHeader Header { get; private set; } = null; + public Action Action { get; private set; } = null; + public Ksh.ParseOption Options { get; private set; } = new Ksh.ParseOption(); public ConverterForm(string path, bool asConverter = false) { @@ -94,9 +95,9 @@ namespace VoxCharger } defaultAscii = AsciiTextBox.Text = Header.Ascii; - BackgroundDropDown.SelectedItem = LastBackground; - VersionDropDown.SelectedIndex = 4; - InfVerDropDown.SelectedIndex = 0; + BackgroundDropDown.SelectedItem = LastBackground; + VersionDropDown.SelectedIndex = 4; + InfVerDropDown.SelectedIndex = 0; charts[main.Difficulty] = new ChartInfo(main, ToLevelHeader(main), target); LoadJacket(charts[main.Difficulty]); @@ -104,36 +105,18 @@ namespace VoxCharger catch (Exception ex) { MessageBox.Show( - $"Failed to load ksh chart.\n{ex.Message}", - "Error", - MessageBoxButtons.OK, + $"Failed to load ksh chart.\n{ex.Message}", + "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error ); - CancelButton.PerformClick(); + Close(); + return; } // Try to locate another difficulty - string dir = Path.GetDirectoryName(target); - foreach (string fn in Directory.GetFiles(dir, "*.ksh")) - { - try - { - var chart = new Ksh(); - chart.Parse(fn); - - // Different chart - if (chart.Title != main.Title) - continue; - - charts[chart.Difficulty] = new ChartInfo(chart, ToLevelHeader(chart), fn); - } - catch (Exception ex) - { - Debug.WriteLine("Failed attempt to parse ksh file: {0} ({1})", fn, ex.Message); - } - } - + charts = GetCharts(target, main); UpdateUI(); } @@ -207,8 +190,7 @@ namespace VoxCharger private void OnProcessConvertButtonClick(object sender, EventArgs e) { - bool warned = false; - var options = new Ksh.ParseOption() + Options = new Ksh.ParseOption() { RealignOffset = RealignOffsetCheckBox.Checked, EnableChipFx = ChipFxCheckBox.Checked, @@ -221,235 +203,16 @@ namespace VoxCharger // Act as converter if (converter) - { - try - { - if (File.Exists(target) || Directory.Exists(target)) - { - if (File.Exists(target)) - SingleConvert(options); - else if (Directory.Exists(target)) - BulkConvert(options); - } - else - { - MessageBox.Show( - "Target path not found", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - - Close(); - } - } - catch (Exception ex) - { - MessageBox.Show( - $"Failed to convert ksh chart.\n{ex.Message}", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - } - - return; - } - - // Again, stupid input get stupid output - foreach (var header in AssetManager.Headers) - { - if (Header.Ascii == header.Ascii) - { - MessageBox.Show( - $"Music Code is already exists.\n{AssetManager.GetMusicPath(header)}", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - - return; - } - } - - // Assign metadata - Header.Ascii = AsciiTextBox.Text; - Header.BackgroundId = short.Parse((BackgroundDropDown.SelectedItem ?? "0").ToString().Split(' ')[0]); - Header.Version = (GameVersion)(VersionDropDown.SelectedIndex + 1); - Header.InfVersion = InfVerDropDown.SelectedIndex == 0 ? InfiniteVersion.MXM : (InfiniteVersion)(InfVerDropDown.SelectedIndex + 1); - Header.GenreId = 16; - Header.Levels = new Dictionary(); - - // Again, stupid input get stupid output - if (Directory.Exists(AssetManager.GetMusicPath(Header))) - { - MessageBox.Show( - $"Music asset for {Header.CodeName} is already exists.", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - - return; - } - - // Uhh, remove empty level? - var entries = charts.Where(x => x.Value != null); - charts = new Dictionary(); - foreach (var entry in entries) - charts[entry.Key] = entry.Value; - - // Assign level info and charts - foreach (var chart in charts) - { - var info = chart.Value; - if (!File.Exists(info.FileName)) - { - MessageBox.Show( - $"Chart file was moved or deleted\n{info.FileName}", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - - charts[chart.Key] = null; - UpdateUI(); - - return; - } - - // If you happen to read the source, this is probably what you're looking for - var ksh = new Ksh(); - ksh.Parse(info.FileName, options); - - var bpmCount = ksh.Events.Count(ev => ev is Event.BPM); - if (!warned && bpmCount > 1 && ksh.MusicOffset % 48 != 0 && options.RealignOffset) - { - // You've been warned! - var prompt = MessageBox.Show( - "Chart contains multiple bpm with music offset that non multiple of 48.\n" + - "Adapting music offset could break the chart.\n\n" + - "Do you want to continue?", - "Warning", - MessageBoxButtons.YesNo, - MessageBoxIcon.Warning - ); - - warned = true; - if (prompt == DialogResult.No) - return; - } - - var vox = new VoxChart(); - vox.Import(ksh); - - var level = ToLevelHeader(ksh); - level.Chart = vox; - - info.Source = ksh; - Header.Levels[chart.Key] = charts[chart.Key].Header = level; - } - - // Prevent resource files being moved or deleted, copy them into temporary storage - string musicFile = string.Empty; - string tmpFile = string.Empty; - foreach (var chart in charts) - { - var info = chart.Value; - - // Make sure to reuse 2dx file for music that share same file - if (string.IsNullOrEmpty(musicFile) || chart.Value.MusicFileName != musicFile) - { - string music = Path.Combine(Path.GetDirectoryName(info.FileName), info.Source.MusicFileName); - if (File.Exists(music)) - { - string tmp = Path.Combine( - Path.GetTempPath(), - $"{Path.GetRandomFileName()}{new FileInfo(info.Source.MusicFileName).Extension}" - ); - - musicFile = music; - info.MusicFileName = tmpFile = tmp; - - File.Copy(music, tmp); - } - else - info.MusicFileName = string.Empty; - } - else - info.MusicFileName = tmpFile; - - string jacket = Path.Combine(Path.GetDirectoryName(info.FileName), info.Source.JacketFileName); - if (File.Exists(jacket)) - { - string tmp = Path.Combine( - Path.GetTempPath(), - $"{Path.GetRandomFileName()}{new FileInfo(info.Source.JacketFileName).Extension}" - ); - - try - { - using (var image = Image.FromFile(jacket)) - info.Header.Jacket = new Bitmap(image); - } - catch (Exception) - { - info.Header.Jacket = null; - } - - info.JacketFileName = tmp; - File.Copy(jacket, tmp); - } - } - - Action = new Action(() => - { - bool unique = false; - musicFile = charts.Values.First().MusicFileName; - foreach (var chart in charts) - { - if (chart.Value.MusicFileName != musicFile) - { - unique = true; - break; - } - } - - // Import all music assets - AssetManager.ImportVox(Header); - - // Make sure to use single asset for music for shared music file - if (!unique) - { - AssetManager.Import2DX(musicFile, Header); - AssetManager.Import2DX(musicFile, Header, true); - } - - foreach (var chart in charts.Values) - { - if (unique && File.Exists(chart.MusicFileName)) - { - AssetManager.Import2DX(chart.MusicFileName, Header, chart.Header.Difficulty); - AssetManager.Import2DX(chart.MusicFileName, Header, chart.Header.Difficulty, true); - } - - if (File.Exists(chart.JacketFileName)) - { - using (var image = Image.FromFile(chart.JacketFileName)) - AssetManager.ImportJacket(Header, chart.Header.Difficulty, new Bitmap(image)); - } - } - }); - - DialogResult = DialogResult.OK; - Close(); + Convert(); + else + Process(); } private VoxHeader ToHeader(Ksh chart) { return new VoxHeader() { - ID = AssetManager.Headers.LastID + 1, + ID = AssetManager.GetNextMusicID(), Title = chart.Title, Artist = chart.Artist, BpmMin = chart.BpmMin, @@ -471,7 +234,280 @@ namespace VoxCharger Level = chart.Level }; } - + + private void Process() + { + try + { + bool warned = false; + foreach (var header in AssetManager.Headers) + { + if (Header.Ascii == header.Ascii) + { + MessageBox.Show( + $"Music Code is already exists.\n{AssetManager.GetMusicPath(header).Replace(AssetManager.GamePath, "")}", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + + return; + } + } + + // Assign metadata + Header.Ascii = AsciiTextBox.Text; + Header.BackgroundId = short.Parse((BackgroundDropDown.SelectedItem ?? "0").ToString().Split(' ')[0]); + Header.Version = (GameVersion)(VersionDropDown.SelectedIndex + 1); + Header.InfVersion = InfVerDropDown.SelectedIndex == 0 ? InfiniteVersion.MXM : (InfiniteVersion)(InfVerDropDown.SelectedIndex + 1); + Header.GenreId = 16; + Header.Levels = new Dictionary(); + + if (Directory.Exists(AssetManager.GetMusicPath(Header))) + { + MessageBox.Show( + $"Music asset for {Header.CodeName} is already exists.", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + + return; + } + + // Uhh, remove empty level? + var entries = charts.Where(x => x.Value != null); + charts = new Dictionary(); + foreach (var entry in entries) + charts[entry.Key] = entry.Value; + + // Assign level info and charts + foreach (var chart in charts) + { + var info = chart.Value; + if (!File.Exists(info.FileName)) + { + MessageBox.Show( + $"Chart file was moved or deleted\n{info.FileName}", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + + charts[chart.Key] = null; + UpdateUI(); + + return; + } + + // If you happen to read the source, this is probably what you're looking for + var ksh = new Ksh(); + ksh.Parse(info.FileName, Options); + + var bpmCount = ksh.Events.Count(ev => ev is Event.BPM); + if (!warned && bpmCount > 1 && ksh.MusicOffset % 48 != 0 && Options.RealignOffset) + { + // You've been warned! + var prompt = MessageBox.Show( + "Chart contains multiple bpm with music offset that non multiple of 48.\n" + + "Adapting music offset could break the chart.\n\n" + + "Do you want to continue?", + "Warning", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning + ); + + warned = true; + if (prompt == DialogResult.No) + return; + } + + // Conversion is actually boring because its already "pre-converted" + var vox = new VoxChart(); + vox.Import(ksh); + + var level = ToLevelHeader(ksh); + level.Chart = vox; + + info.Source = ksh; + Header.Levels[chart.Key] = charts[chart.Key].Header = level; + } + + // Prevent resource files being moved or deleted, copy them into temporary storage + string musicFile = string.Empty; + string tmpFile = string.Empty; + foreach (var chart in charts) + { + var info = chart.Value; + + // Make sure to reuse 2dx file for music that share same file + if (string.IsNullOrEmpty(musicFile) || chart.Value.MusicFileName != musicFile) + { + string music = Path.Combine(Path.GetDirectoryName(info.FileName), info.Source.MusicFileName); + if (File.Exists(music)) + { + string tmp = Path.Combine( + Path.GetTempPath(), + $"{Path.GetRandomFileName()}{new FileInfo(info.Source.MusicFileName).Extension}" + ); + + musicFile = music; + info.MusicFileName = tmpFile = tmp; + + File.Copy(music, tmp); + } + else + info.MusicFileName = string.Empty; + } + else + info.MusicFileName = tmpFile; + + string jacket = Path.Combine(Path.GetDirectoryName(info.FileName), info.Source.JacketFileName); + if (File.Exists(jacket)) + { + string tmp = Path.Combine( + Path.GetTempPath(), + $"{Path.GetRandomFileName()}{new FileInfo(info.Source.JacketFileName).Extension}" + ); + + try + { + using (var image = Image.FromFile(jacket)) + info.Header.Jacket = new Bitmap(image); + } + catch (Exception) + { + info.Header.Jacket = null; + } + + info.JacketFileName = tmp; + File.Copy(jacket, tmp); + } + } + + Action = new Action(() => + { + bool unique = false; + musicFile = charts.Values.First().MusicFileName; + foreach (var chart in charts) + { + if (chart.Value.MusicFileName != musicFile) + { + unique = true; + break; + } + } + + // Import all music assets + AssetManager.ImportVox(Header); + + // Make sure to use single asset for music for shared music file + if (!unique) + { + AssetManager.Import2DX(musicFile, Header); + AssetManager.Import2DX(musicFile, Header, true); + } + + foreach (var chart in charts.Values) + { + if (unique && File.Exists(chart.MusicFileName)) + { + AssetManager.Import2DX(chart.MusicFileName, Header, chart.Header.Difficulty); + AssetManager.Import2DX(chart.MusicFileName, Header, chart.Header.Difficulty, true); + } + + if (File.Exists(chart.JacketFileName)) + { + using (var image = Image.FromFile(chart.JacketFileName)) + AssetManager.ImportJacket(Header, chart.Header.Difficulty, new Bitmap(image)); + } + } + }); + + + DialogResult = DialogResult.OK; + Close(); + } + catch (Exception ex) + { + MessageBox.Show( + $"Failed to import ksh chart.\n{ex.Message}", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } + + private void Convert() + { + // Only serve as options dialog + if (string.IsNullOrEmpty(target)) + { + DialogResult = DialogResult.OK; + Close(); + } + + try + { + if (File.Exists(target) || Directory.Exists(target)) + { + if (File.Exists(target)) + SingleConvert(Options); + else if (Directory.Exists(target)) + BulkConvert(Options); + } + else + { + MessageBox.Show( + "Target path not found", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + + DialogResult = DialogResult.Cancel; + Close(); + } + } + catch (Exception ex) + { + MessageBox.Show( + $"Failed to convert ksh chart.\n{ex.Message}", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + } + + private Dictionary GetCharts(string target, Ksh main) + { + // Try to locate another difficulty + string dir = Path.GetDirectoryName(target); + var charts = new Dictionary(); + foreach (string fn in Directory.GetFiles(dir, "*.ksh")) + { + try + { + var chart = new Ksh(); + chart.Parse(fn); + + // Different chart + if (chart.Title != main.Title) + continue; + + charts[chart.Difficulty] = new ChartInfo(chart, ToLevelHeader(chart), fn); + } + catch (Exception ex) + { + Debug.WriteLine("Failed attempt to parse ksh file: {0} ({1})", fn, ex.Message); + } + } + + return charts; + } + private void LoadJacket(ChartInfo info) { var chart = info.Source; @@ -608,6 +644,7 @@ namespace VoxCharger MessageBoxIcon.Information ); + DialogResult = DialogResult.OK; Close(); } } @@ -692,6 +729,7 @@ namespace VoxCharger MessageBoxIcon.Information ); + DialogResult = DialogResult.OK; Close(); } } diff --git a/Sources/Forms/MainForm.Designer.cs b/Sources/Forms/MainForm.Designer.cs index 5b6a860..2f887ce 100644 --- a/Sources/Forms/MainForm.Designer.cs +++ b/Sources/Forms/MainForm.Designer.cs @@ -858,9 +858,9 @@ this.IdTextBox.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.IdTextBox.Location = new System.Drawing.Point(83, 23); this.IdTextBox.Name = "IdTextBox"; + this.IdTextBox.ReadOnly = true; this.IdTextBox.Size = new System.Drawing.Size(383, 21); this.IdTextBox.TabIndex = 1; - this.IdTextBox.TextChanged += new System.EventHandler(this.OnMetadataChanged); // // IdLabel // diff --git a/Sources/Forms/MainForm.cs b/Sources/Forms/MainForm.cs index 30e60a5..a5f4477 100644 --- a/Sources/Forms/MainForm.cs +++ b/Sources/Forms/MainForm.cs @@ -107,13 +107,15 @@ namespace VoxCharger { try { - Save(AssetManager.MdbFilename); - MessageBox.Show( - "Mix has been saved successfully", - "Information", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); + if (Save(AssetManager.MdbFilename)) + { + MessageBox.Show( + "Mix has been saved successfully", + "Information", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } } catch (Exception ex) { @@ -139,13 +141,15 @@ namespace VoxCharger if (exporter.ShowDialog() != DialogResult.OK) return; - Save(exporter.FileName); - MessageBox.Show( - "Mix has been saved successfully", - "Information", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); + if (Save(exporter.FileName)) + { + MessageBox.Show( + "Mix has been saved successfully", + "Information", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } } } catch (Exception ex) @@ -491,7 +495,16 @@ namespace VoxCharger return; if (int.TryParse(IdTextBox.Text, out int id)) - header.ID = id; + { + // Validate ID + if (!AssetManager.ValidateMusicID(id)) + { + IdTextBox.Text = header.ID.ToString(); + MessageBox.Show("Music ID is already taken", "Duplicate", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + else + header.ID = id; + } else IdTextBox.Text = header.ID.ToString(); @@ -703,19 +716,21 @@ namespace VoxCharger } } - private void Save(string dbFilename) + private bool Save(string dbFilename) { + var errors = new List(); using (var loader = new LoadingForm()) { var proc = new Action(() => { int max = actions.Count + 1; - foreach (var queue in actions.Values) + foreach (var action in actions) { float progress = ((float)(max - actions.Count) / max) * 100f; loader.SetStatus($"[{progress:00}%] - Processing assets.."); loader.SetProgress(progress); + var queue = action.Value; while (queue.Count > 0) { try @@ -724,6 +739,7 @@ namespace VoxCharger } catch (Exception ex) { + errors.Add($"{action.Key}: {ex.Message}"); Debug.WriteLine(ex.Message); } } @@ -740,8 +756,19 @@ namespace VoxCharger loader.ShowDialog(); } + if (errors.Count > 0) + { + string message = "Error occured when processing following assets:\n"; + foreach (var err in errors) + message += $"\n{err}"; + + MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + actions.Clear(); Pristine = true; + + return errors.Count == 0; } private void Import2DX(bool preview = false) @@ -880,6 +907,8 @@ namespace VoxCharger control.Enabled = safe; } + // Modifying this could lead into disaster, must be left untouched + IdTextBox.ReadOnly = true; LevelGroupBox.Enabled = true; foreach (Control control in LevelGroupBox.Controls) { diff --git a/Sources/Vox/MusicDb.cs b/Sources/Vox/MusicDb.cs index e954845..915354e 100644 --- a/Sources/Vox/MusicDb.cs +++ b/Sources/Vox/MusicDb.cs @@ -15,10 +15,11 @@ namespace VoxCharger private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("Shift_JIS"); private Dictionary headers; - - public int LastID => headers.Count > 0 ? headers.Values.Max(h => h.ID) : 0; + private int max = 0; - public int Count => headers.Count; + public int Count => headers.Count; + + public int LastID => headers.Values.Max(h => h.ID); public MusicDb() { @@ -178,17 +179,33 @@ namespace VoxCharger public void Add(VoxHeader header) { + if (max < header.ID) + max = header.ID; + headers[header.ID] = header; } public void Remove(int id) { + if (max == id) + max = 0; + headers.Remove(id); } public void Remove(VoxHeader header) { - headers.Remove(header.ID); + Remove(header.ID); + } + + public bool Contains(int id) + { + return headers.ContainsKey(id); + } + + public bool Contains(VoxHeader header) + { + return Contains(header.ID); } public VoxHeader GetHeader(int id)